本文主要是探讨xLua下C#调用Lua的实现原理,有关Lua如何调用C#的介绍可以查看深入xLua实现原理之Lua如何调用C#
C#与Lua数据通信机制无论是Lua调用C#,还是C#调用Lua,都需要一个通信机制,来完成数据的传递。而Lua本身就是由C语言编写的,所以它出生自带一个和C/C++的通信机制。
Lua和C/C++的数据交互通过栈进行,操作数据时,首先将数据拷贝到"栈"上,然后获取数据,栈中的每个数据通过索引值进行定位,索引值为正时表示相对于栈底的偏移索引,索引值为负时表示相对于栈顶的偏移索引,索引值以1或-1为起始值,因此栈顶索引值永远为-1, 栈底索引值永远为1 。 “栈"相当于数据在Lua和C/C++之间的中转地。每种数据都有相应的存取接口。
而C#可以通过P/Invoke方式调用Lua的dll,通过这个dll执行Lua的C API。换言之C#可以借助C/C++来与Lua进行数据通信。在xLua的LuaDLL.cs文件中可以找到许多DllImport修饰的数据入栈与获取的接口。
// LuaDLL.cs [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] public static extern void lua_pushnumber(IntPtr L, double number); [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] public static extern void lua_pushboolean(IntPtr L, bool value); [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] public static extern void xlua_pushinteger(IntPtr L, int value); [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] public static extern double lua_tonumber(IntPtr L, int index); [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] public static extern int xlua_tointeger(IntPtr L, int index); [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] public static extern uint xlua_touint(IntPtr L, int index); [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] public static extern bool lua_toboolean(IntPtr L, int index);除了普通的值类型之外,Lua中比较特殊但又很常用的大概就是table和funciton这两种类型了,下面逐一来分析
传递Lua table到C#以TestXLua类为例来看Lua table是如何被传递的,TestXLua有一个LuaTable类型的静态变量,LuaTable是C#这边定义的一个类,封装了一些对Lua table的操作
// 注意,这里添加的LuaCallCSharp特性只是为了使xLua为其生成代码,不添加并不影响功能 [LuaCallCSharp] public class TestXLua { public static LuaTable tab; }在点击Generate Code之后,部分生成代码如下所示。为tab变量生成了对应的set和get包裹方法
// TestXLuaWrap.cs [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] static int _g_get_tab(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); translator.Push(L, TestXLua.tab); } catch(System.Exception gen_e) { return LuaAPI.luaL_error(L, "c# exception:" + gen_e); } return 1; } [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] static int _s_set_tab(RealStatePtr L) { try { ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); TestXLua.tab = (XLua.LuaTable)translator.GetObject(L, 1, typeof(XLua.LuaTable)); } catch(System.Exception gen_e) { return LuaAPI.luaL_error(L, "c# exception:" + gen_e); } return 0; }为tab静态变量赋值一个Lua table,table中包含一个 num = 1 键值对
-- Lua测试代码 local t = { num = 1 } CS.TestXLua.tab = t上述代码在赋值时,最终会调用到_s_set_tab包裹方法(具体原理可以查看这里),Lua这边调用_s_set_tab前,会先将参数table t压入到栈中,因此_s_set_tab内部需要通过translator.GetObject拿到这个table,并将其赋值给tab静态变量
// ObjectTranslator.cs public object GetObject(RealStatePtr L, int index, Type type) { int udata = LuaAPI.xlua_tocsobj_safe(L, index); if (udata != -1) { // 对C#对象的处理 object obj = objects.Get(udata); RawObject rawObject = obj as RawObject; return rawObject == null ? obj : rawObject.Target; } else { if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA) { GetCSObject get; int type_id = LuaAPI.xlua_gettypeid(L, index); if (type_id != -1 && type_id == decimal_type_id) { decimal d; Get(L, index, out d); return d; } Type type_of_struct; if (type_id != -1 && typeMap.TryGetValue(type_id, out type_of_struct) && type.IsAssignableFrom(type_of_struct) && custom_get_funcs.TryGetValue(type, out get)) { return get(L, index); } } return (objectCasters.GetCaster(type)(L, index, null)); } }GetObject方法负责从栈中获取指定类型的对象,对于LuaTable类型是通过objectCasters.GetCaster获取转换器后,通过转换器函数转换得到
// ObjectTranslator.cs public ObjectCast GetCaster(Type type) { if (type.IsByRef) type = type.GetElementType(); // 如果是按引用传递的,则使用引用的对象的type Type underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { return genNullableCaster(GetCaster(underlyingType)); } ObjectCast oc; if (!castersMap.TryGetValue(type, out oc)) { oc = genCaster(type); castersMap.Add(type, oc); } return oc; }