7.5 CLR/Lua

The Limax provides a clrlua project, which combines the C# with the Lua, supports the interoperability of the code between the C# and the Lua, to implement the LuaScriptHandle under the frame of the C# script mode. This chapter describes the functions provided by the clrlua.


  • 7.5.3 Type mapping

    There is no compabitility between the C# type system and the Lua type system. So the type mapping can not be described in a clear form. It is described in 3 rules.


    • Rule #1: C# transfers to Lua (the C# transfers objects to the Lua through eval; when the Lua accesses the C#, construct the return object, the return value of the method and the return value of the delegation, read the field, and read the attribute)

      In this situation, the type information of the C# could be clearly obtained.

      • null, use lua_pushinil to transfer

      • bool and boxing type Boolean, use lua_pushboolean to transfer

      • byte, sbyte, short, ushort, int, uint, long, ulong, and boxing type Byte, SByte, Int16, Uint16, Int32, UInt32, Int64, Uint64, use lua_pushinteger to transfer

      • float, double, decimal and boxing type Single, Double, Decimal, use lua_pushnumber to transfer

      • char, string and boxing type Char, String, UTF8 after encoding, use lua_pushstring to tranfer. It should be noted that the string has changed to the Lua string after transfered to the Lua, and could not call the string methods of the C# to access. In the previous example which accesses the field, if execute lua.eval("print(t0.mystr.Length)"), the nil will be output. Using lua.eval("print(t0.mystr:len())") can get the correct result. The syntax is weird, be careful.

      • The types except for the LuaObject, LuaTable and LuaFunction, use lua_newuserdata to create a user data type to associate. Particularly, if the type is the delegation type, the Invoke method of the delegation object is obtained as the object that is actually associated with it, so that the delegation object can be correctly called.

      • The LuaObject, LuaTable and LuaFunction are the type returned to the C# previously, then get back the associated Lua object.

      • In particular, the void returned by the C#'s method, or the void returned by the delegation, or reading the non-readable attribute. It will not push any data on the stack, and reference to the experiment on the charpter <The specification that the scripting language accesses C#> for the details.


    • Rule #2: Lua returns the value to C# (the returned object of the eval)

      In this situation, the return is only based on the Lua's current known information. When C# gets this kind of return value, it should strictly check the type and then use it, or the type conversion exception System.InvalidCastException might be caused.

      • Lua_TNIL, return null

      • LUA_TBOOLEAN, return Boolean

      • LUA_TSTRING, return String after the UTF8 decoding

      • LUA_TNUMBER, return Int64 or Double according to the lua_isinteger. It is very special because there is no LUA_TINTEGER type, and it might that the Lua addes this patch to support the Int64 after ensuring the compatibility -- except for the type information, a tag is added internal to distinguish without effecting the original type specification.

      • LUA_TUSERDATA, the non-numeric object transferred by C# previously. It should be directly returned to the C#. Particularly, if the object is a delegate encapsulation, the delegation itself is returned.

      • LUA_TTABLE, a Lua table, create and return the associated LuaTable object. The C# could directly access the table via the IDictionary interface of the LuaTable. Accessing the LuaTable object bases on the Lua specification, not the IDictionary specification. For example, the exception is thrown when IDictionary repeats the Add, and the replacement is executed when the Lua repeats the Add. Adding nil in the Lua means deleating. So LuaTable.Add(key,null) equals LuaTable.Remve(key).

      • LUA_TFUNCTION, a Lua function, create an associated object derived from the LuaObject. Then this object uses the LuaFunction delegation to encapsulate so that the C# could forward the function call to the Lua virtual machine to call the responding Lua function.

      • Particularly, the DBNull.Value is returned when there is no return value. If there is multiple return values, the packaged object array of the C# is returned after each return value is converted to the relative C# object according to the previous way.

      • Note: the converison such as (int)lua.eval("return 1"); inevitably causes the System.InvalidCastException, because that the lua_Integer is the 64 bits corresponding to the long and the return type is System.Int64. So this kind of boxing type could not be directly convert to the int like the simple type long. Particularly, if the type is the delegation type, the Invoke method of the delegation object is obtained as the object that is actually associated to it, so that the delegation object can be correctly called. The getType() method could be called on the returned object to check the actual type, and determine whether it meets the requirement and then process the correct conversion.


    • Rule #3: Lua transfers to C# (parameters of the constructor called, parameters of the method called, parameters of the delegation called, field set, property set)

      First of all, convert to the C# object according to the rule #2, then make the conversion in the C# according to the expected parameter type. Refer to the charpter <The specification that the scripting language accesses C#> for the details.


  • 7.5.4 Exception specification

    • The running time execption of the Lua code is caught by the Lua virtual machine and transferred to the C# through the passed delegation when Lua constructs the object.

    • When the Lua calls the C# construct function, or member function, or delegation, if the exception is thrown, this exception is internally caught and converted to the string format of C# exception, then adds the Lua stack description as the surffix, generates the error information, submitts to the Lua virtual machine, and finally is transferred to the C#. A problem should be emphasized here that under the C# and Lua call each other and is in indirect recursion situation, the exception actually could not reach the top of the entire recursion stack, but interrupt execution of the current eval, send out the exception string, and finally return the DBNull.Value through eval. If there is the requirement to rollback the entire recursion stack, it could design like this: avoid the eval to return DBNull.Value logically, once the eval returns the DBNull.Value, the exception with the last error information is thrown in the C#.


  • 7.5.5 Matters need attention

    • 1. The generic object of the C# could not be transferred to the Lua virtual machine, or there is exception thrown. The reason is that System::Runtime::InteropServices::Marshal::GetNativeVariantForObject, System::Runtime::InteropServices::Marshal::GetObjectForNativeVariant, these two key functions' generic version is provided from the .NET4.5.1, and this project use .NET4.0 to develop (.NET3.5 also works) for the compatibility with the older versions of .NET.

    • 2. When the LuaObject returns to the C#, it will be controlled by the gc of the C#. The gc thread does not necessarily operate the thread of the Lua virtual machine, so this project must be designed as thread safe project. The implementation ensures the safe through adding lock on the Lua operation object. In order to lock when the LuaObject destructs, it needs to transfer the reference of the Lua operation object to the LuaObject constructor. Such a transfer can only be achieved via lua_State structure. However, the lua_State structure lacks of a field with the user-defined pointer, so the lua_State.hook pointer is used, which is equivalent to throwing away the hook ability of the Lua, and the debug.sethook and debug.gethook two methods are also thrown away to avoid the user to use and cause the error. If it needs to keep the hook ability, it only modifies the Lua source code, add a user-defined pointer on the lua_State structure, modifies the corresponding clrlua.cpp, and recompilies the project.

    • 3. It could create multiple Lua operation objects. The LuaObject and LuaTable obtained from the one Lua operation object could not be transferred to another Lua operation object, which is obvious. There is no limitation for the other C# object to transfer multiple Lua operation objects. If different thread uses the different Lua operation object, the thread safe of these transferred C# objects should be ensured by themself. If the multiple Lua operation objects have the data exchange requirement, it could be implemented through JSON, and the Limax has its own json.lua. The limitation is that there is no loop between the objects.

    • 4. It should be noted when accessing the table of the Lua virtual machine through LuaTable in the multiple threads. Before using foreach method to iterate the table, the lock must be added on the lua operate object or LuaTable.SyncRoot, and the two actually points to the same object. The other access methods are safe internally by locking.

    • 5. The json.lua is integrated in the project, and the global name space has the JSON.stringify, JSON.parse, which could be directly used.


Prev Next