|
上一节我们讨论了C#是如何访谒lua变量的,此次我们将研究lua是如何访谒到C#函数的。同样也是来看下官方的例子,example 08:- string script =
- @”
- function TestArray(array)
- local len = array.Length
- for i = 0, len - 1 do
- print('Array: '..tostring(array[i]))
- end
- local iter = array:GetEnumerator()
- while iter:MoveNext() do
- print('iter: '..iter.Current)
- end
- local t = array:ToTable()
- for i = 1, #t do
- print('table: '.. tostring(t[i]))
- end
- local pos = array:BinarySearch(3)
- print('array BinarySearch: pos: '..pos..' value: '..array[pos])
- pos = array:IndexOf(4)
- print('array indexof bbb pos is: '..pos)
- return 1, '123', true
- end
- ”;
- LuaState lua = null;
- LuaFunction func = null;
- new LuaResLoader();
- lua = new LuaState();
- lua.Start();
- lua.DoString(script, ”AccessingArray.cs”);
- int[] array = { 1, 2, 3, 4, 5 };
- func = lua.GetFunction(”TestArray”);
- func.BeginPCall();
- func.Push(array);
- func.PCall();
- func.EndPCall();
复制代码 这个例子中,lua代码的TestArray函数接收一个array参数,它是来自C#的数组。我们仿佛就在C#层一样,可以直接调用array的方式,例如GetEnumerator;还可以直接使用下标访谒,array就能取出对应下标的元素;还可以直接使用array的get/set属性,例如Length。运行成果如下图:
tolua源码分析(四)例子运行成果
那么,这一切是如何完成的呢?让我们回忆一下tolua的初始化流程,在C#的LuaState类的构造函数中,有一个OpenBaseLibs的调用,它包含了一些最基本C#类的注册,此中就有我们这里要用到的System.Array。这里只截取了与System.Array相关的代码:- void OpenBaseLibs()
- {
- BeginModule(null);
- BeginModule(”System”);
- System_ArrayWrap.Register(this);
- EndModule();//end System
- EndModule(); //end global
- ArrayMetatable = metaMap[typeof(System.Array)];
- }
复制代码 BeginModule和EndModule是一组配对函数,用来将C#的namespace注册给lua。BeginModule会调到C层的tolua_beginmodule函数,一开始我们的参数为null,暗示我们即将向全局的namespace中注册各种东西,也就是筹备往lua层的_G中塞东西,那么对应的tolua_beginmodule实现也非常简单,就是将lua层的_G筹备好,此时的lua栈如图所示:- LUALIB_API bool tolua_beginmodule(lua_State *L, const char *name)
- {
- if (name != NULL)
- {
- ...
- }
- else
- {
- lua_pushvalue(L, LUA_GLOBALSINDEX);
- return true;
- }
- }
复制代码
tolua源码分析(四)调用BeginModule(null)时的lua栈
接下来,调用的是BeginModule(”System”),此时对应的tolua_beginmodule实现会稍微复杂一些,我们将一步步地模拟栈操作,对当前的lua栈进行可视化:- LUALIB_API bool tolua_beginmodule(lua_State *L, const char *name)
- {
- if (name != NULL)
- {
- lua_pushstring(L, name); //stack key
- lua_rawget(L, -2); //stack value
- if (lua_isnil(L, -1))
- {
- lua_pop(L, 1);
- lua_newtable(L); //stack table
- lua_pushstring(L, ”__index”);
- lua_pushcfunction(L, module_index_event);
- lua_rawset(L, -3);
- lua_pushstring(L, name); //stack table name
- lua_pushstring(L, ”.name”); //stack table name ”.name”
- pushmodule(L, name); //stack table name ”.name” module
- lua_rawset(L, -4); //stack table name
- lua_pushvalue(L, -2); //stack table name table
- lua_rawset(L, -4); //stack table
- lua_pushvalue(L, -1);
- lua_setmetatable(L, -2);
- return true;
- }
- else if (lua_istable(L, -1))
- {
- ...
- }
- }
- else
- {
- ...
- }
- }
复制代码 第5-6行判断_G中是否已经存在名为System的table,如果有就直接取出,不用再新建了,当然此时我们是没有的,所以会走到第8行,新建一个table出来,此时的lua栈如下图所示:
tolua源码分析(四)调用BeginModule("System")时的lua栈
第13-15行设置了这个table的__index域,显然这个table会被拿来用作metatable使用,这里的module_index_event函数我们等用到的时候再展开说。接下来17-18行持续两个pushstring,显然是为了记录这个table的name,此时的lua栈如图所示:
tolua源码分析(四)当前lua栈
第19行来了个pushmodule函数,这个函数使用了一个buffer来缓存当前注册过程中已经注册过的namespace,这样就能够通过拼接得到当前namespace的完整名称,这里namespace System就已经是完整名称了,因此push进lua栈的还是System,此时的lua栈如图所示:
tolua源码分析(四)当前lua栈
第20行设置了这个table的.name域为System,它暗示对应C# namespace的完整名称。紧接着第21行又把table复制了一份,压到了栈顶:
tolua源码分析(四)当前lua栈
第22行就是把namespace信息保留到_G了,简单来说就是_G[”System”] = new table,最后第24-25行,就是把该table的metatable设置为它自身,函数执行结束时,会在栈上留下一份该table,这样做的目的是让该namespace下的class及namespace都能够关联到table上。
tolua源码分析(四)tolua_beginmodule结束时的lua栈
总的来说,tolua_beginmodule做的事情,就是创建了一个table,设置table的__index,.name还有metatable,此中.name是table对应C# namespace的全称,metatable为它自身。
接下来就是重头戏System_ArrayWrap.Register,System_ArrayWrap是tolua自动生成用来将C#的System.Array注册到lua层的类,Register是它的静态方式,里面包含了需要注册到lua层的方式,属性,以及下标访谒等操作:- public static void Register(LuaState L)
- {
- L.BeginClass(typeof(Array), typeof(System.Object));
- L.RegFunction(”.geti”, get_Item);
- L.RegFunction(”.seti”, set_Item);
- L.RegFunction(”ToTable”, ToTable);
- L.RegFunction(”GetLength”, GetLength);
- L.RegFunction(”GetLongLength”, GetLongLength);
- L.RegFunction(”GetLowerBound”, GetLowerBound);
- L.RegFunction(”GetValue”, GetValue);
- L.RegFunction(”SetValue”, SetValue);
- L.RegFunction(”GetEnumerator”, GetEnumerator);
- L.RegFunction(”GetUpperBound”, GetUpperBound);
- L.RegFunction(”CreateInstance”, CreateInstance);
- L.RegFunction(”BinarySearch”, BinarySearch);
- L.RegFunction(”Clear”, Clear);
- L.RegFunction(”Clone”, Clone);
- L.RegFunction(”Copy”, Copy);
- L.RegFunction(”IndexOf”, IndexOf);
- L.RegFunction(”Initialize”, Initialize);
- L.RegFunction(”LastIndexOf”, LastIndexOf);
- L.RegFunction(”Reverse”, Reverse);
- L.RegFunction(”Sort”, Sort);
- L.RegFunction(”CopyTo”, CopyTo);
- L.RegFunction(”ConstrainedCopy”, ConstrainedCopy);
- L.RegFunction(”__tostring”, ToLua.op_ToString);
- L.RegVar(”Length”, get_Length, null);
- L.RegVar(”LongLength”, get_LongLength, null);
- L.RegVar(”Rank”, get_Rank, null);
- L.RegVar(”IsSynchronized”, get_IsSynchronized, null);
- L.RegVar(”SyncRoot”, get_SyncRoot, null);
- L.RegVar(”IsFixedSize”, get_IsFixedSize, null);
- L.RegVar(”IsReadOnly”, get_IsReadOnly, null);
- L.EndClass();
- }
复制代码 与namespace类似,class的注册也需要一组配对方式BeginClass和EndClass,此中属性相关的注册是使用RegVar方式,方式和索引操作的注册是使用RegFunction方式。那么首先从BeginClass看起,它接受两个type参数,分袂暗示当前注册的类型与其基类,这里System.Object是Array的基类:- public int BeginClass(Type t, Type baseType, string name = null)
- {
- if (beginCount == 0)
- {
- throw new LuaException(”must call BeginModule first”);
- }
- int baseMetaRef = 0;
- int reference = 0;
- if (name == null)
- {
- name = GetToLuaTypeName(t);
- }
- if (baseType != null && !metaMap.TryGetValue(baseType, out baseMetaRef))
- {
- LuaCreateTable();
- baseMetaRef = LuaRef(LuaIndexes.LUA_REGISTRYINDEX);
- BindTypeRef(baseMetaRef, baseType);
- }
- if (metaMap.TryGetValue(t, out reference))
- {
- LuaDLL.tolua_beginclass(L, name, baseMetaRef, reference);
- RegFunction(”__gc”, Collect);
- }
- else
- {
- reference = LuaDLL.tolua_beginclass(L, name, baseMetaRef);
- RegFunction(”__gc”, Collect);
- BindTypeRef(reference, t);
- }
- return reference;
- }
复制代码 函数首先会进行一次查抄,即第3-6行,BeginModule必需要在BeginClass之前调用,否则生成的class table没法子绑定到namespace table上;第11-14行,如果函数传参时没有指定注册的class的name,那这里会使用GetToLuaTypeName进行生成,这里生成的name就是Array;第16-21行,如果函数传参时指定了基类baseType,那么需要查抄baseType是否已经注册过了。这里的metaMap是一个key为Type,value为int的dictionary,此中key暗示当前已经注册过的class类型,value暗示该类型在lua层的reference。如果baseType已经注册过,那直接将其reference取出即可;如果尚未注册,就先创建一个空的table,用这个空table向lua层获取reference,得到的reference就作为baseType在lua层的reference,记录到metaMap中;这里在注册Array时,已经在之前注册过System.Object了,因此直接就能拿到System.Object的reference了;第23-33行,就是判断当前注册的类是否已经有reference了,由我们方才讨论可知,如果先注册子类,再注册基类,那么在注册基类时reference就会存在,不管怎样,最后函数城市走到tolua_beginclass上,无非就是传不传reference的区别。
tolua_beginclass在C层实现,相对也斗劲复杂:- LUALIB_API int tolua_beginclass(lua_State *L, const char *name, int baseType, int ref)
- {
- int reference = ref;
- lua_pushstring(L, name);
- lua_newtable(L);
- _addtoloaded(L);
- if (ref == LUA_REFNIL)
- {
- lua_newtable(L);
- lua_pushvalue(L, -1);
- reference = luaL_ref(L, LUA_REGISTRYINDEX);
- }
- else
- {
- lua_getref(L, reference);
- }
- if (baseType != 0)
- {
- lua_getref(L, baseType);
- lua_setmetatable(L, -2);
- }
- lua_pushlightuserdata(L, &tag);
- lua_pushnumber(L, 1);
- lua_rawset(L, -3);
- lua_pushstring(L, ”.name”);
- _pushfullname(L, -4);
- lua_rawset(L, -3);
- lua_pushstring(L, ”.ref”);
- lua_pushinteger(L, reference);
- lua_rawset(L, -3);
- lua_pushstring(L, ”__call”);
- lua_pushcfunction(L, class_new_event);
- lua_rawset(L, -3);
- tolua_setindex(L);
- tolua_setnewindex(L);
- return reference;
- }
复制代码 第3-5行把class的name与新建的class table压入了lua栈:
tolua源码分析(四)调用tolua_beginclass时的lua栈
第6行的_addtoloaded函数,顾名思义,就是将class table保留到package.loaded里,用lua代码描述就是:package.loaded[”System.Array”] = table。第8-17行,按照当前类型的reference是否存在,如果存在直接调用lua_getref取出对应的table,否则就再新建一个table,作为该类型的reference table,同时得到reference,此时lua栈如图所示:
tolua源码分析(四)当前lua栈
第19-23行判断当前类型是否有基类,如果有基类的话,要把基类的reference table取出,作为当前类型reference table的metatable,这样做的目的是为了在lua层实现对C#基类的访谒,比如lua层的System.Array对象可以直接访谒Sytem.Object类中注册过的方式和属性。
第25-27行为reference table打上了特殊的tag,table中插入了一个特殊的lightuserdata类型的key,拥有这个key的table即为reference table;第29-31行在table中设置class的全称,这里就是System.Array;第33-35行在table中记录了reference的值;第37-39行设置了__call对应的方式,有了这个我们在lua层便利地使用System.Array()来创建一个Array对象了。从这里也能猜出,这个reference table是作为metatable使用的。函数的最后,tolua_setindex和tolua_setnewindex设置了__index和__newindex,分袂用来从C#读取类对象的信息到lua层,以及从lua层写入类对象的信息到C#层。
到这,BeginClass差不多结束了,总的来说就是往栈上新增了class的名称,以及两个table,此中一个table中设置了class的全称,reference,以及用作metatable的__call,__index,__newindex方式。那此刻回过头来看看方式和索引操作的注册RegFunction的实现:- public void RegFunction(string name, LuaCSFunction func)
- {
- IntPtr fn = Marshal.GetFunctionPointerForDelegate(func);
- LuaDLL.tolua_function(L, name, fn);
- }
复制代码 函数实现异常简单,主要逻辑在C层的tolua_function中:- LUALIB_API void tolua_function(lua_State *L, const char *name, lua_CFunction fn)
- {
- lua_pushstring(L, name);
- tolua_pushcfunction(L, fn);
- lua_rawset(L, -3);
- }
复制代码 也很简单,就是往栈顶里的table塞数据,而由前面的图可知,此时lua栈顶的table为该类型的reference table,用lua代码描述Register中的RegFunction操作,也就是:- ref[”.geti”] = get_Item
- ref[”.seti”] = set_Item
- ref[”ToTable”] = ToTable
- ...
复制代码 下面再看下注册属性的RegVar实现:- public void RegVar(string name, LuaCSFunction get, LuaCSFunction set)
- {
- IntPtr fget = IntPtr.Zero;
- IntPtr fset = IntPtr.Zero;
- if (get != null)
- {
- fget = Marshal.GetFunctionPointerForDelegate(get);
- }
- if (set != null)
- {
- fset = Marshal.GetFunctionPointerForDelegate(set);
- }
- LuaDLL.tolua_variable(L, name, fget, fset);
- }
复制代码 可以看到get和set是分隔的,与C#层保持一致,只有C#层的get/set属性存在,才会注册到lua层。来看下C层的tolua_variable:- LUALIB_API void tolua_variable(lua_State *L, const char *name, lua_CFunction get, lua_CFunction set)
- {
- lua_pushlightuserdata(L, &gettag);
- lua_rawget(L, -2);
- if (!lua_istable(L, -1))
- {
- /* create .get table, leaving it at the top */
- lua_pop(L, 1);
- lua_newtable(L);
- lua_pushlightuserdata(L, &gettag);
- lua_pushvalue(L, -2);
- lua_rawset(L, -4);
- }
- lua_pushstring(L, name);
- tolua_pushcfunction(L, get);
- lua_rawset(L, -3); /* store variable */
- lua_pop(L, 1); /* pop .get table */
- /* set func */
- if (set != NULL)
- {
- lua_pushlightuserdata(L, &settag);
- lua_rawget(L, -2);
- if (!lua_istable(L, -1))
- {
- /* create .set table, leaving it at the top */
- lua_pop(L, 1);
- lua_newtable(L);
- lua_pushlightuserdata(L, &settag);
- lua_pushvalue(L, -2);
- lua_rawset(L, -4);
- }
- lua_pushstring(L, name);
- tolua_pushcfunction(L, set);
- lua_rawset(L, -3); /* store variable */
- lua_pop(L, 1); /* pop .set table */
- }
- }
复制代码 类似地,为了保证table的key的独一性,以及这个key不成能被lua层访谒到,这里再次使用lightuserdata作为reference table的key,第4-14行测验考试从reference table中取出用来存储get属性的get table,如果存在就压入栈顶,如果不存在,则新建一个,以lightuserdata为key插入到reference table中;第16-18行,把get属性对应的方式绑定到get table中,即get[name] = get_function。完成这一步后,第19行将get table从栈顶弹出,get属性的注册算是结束了。set属性的操作与之类似,这里就不再反复描述了。
自此,reference table中包含了注册的C#函数信息,以及get和set这两个table,分袂包含了注册的C# get/set属性信息。最后就剩下tolua_endclass了:- LUALIB_API void tolua_endclass(lua_State *L)
- {
- lua_setmetatable(L, -2);
- lua_rawset(L, -3);
- }
复制代码 函数做了两件事,一是把reference table设置为class table的metatable:
tolua源码分析(四)当前lua栈
二是把class table塞到namespace中,这里就是_G.System[”Array”] = class。自此class的注册过程彻底结束,lua栈也恢复到了注册前的模样:
tolua源码分析(四)tolua_beginclass结束时的lua栈
我们之前提到过,在注册namespace时,我们把注册的table的metatable设置为它自身,为什么在注册class的时候,不这样做,而是此外用了一个reference table来作为class table的metatable呢?这是因为,我们在lua层除了直接访谒class之外,更多可能是通过C# object访谒到class,也就是说,存在两种分歧的方式访谒到注册的C#方式。因此,为了区分这两种情况,需要把保留注册C#方式的table给单独抽出来,class直接访谒就通过class table的形式,例如System.Array(),object访谒通过userdata的形式, 例如array.Length。
我们花了这么长的篇幅做了铺垫,此刻回到例子中的lua代码,它有个array的参数,这个参数是从C#层push进去的,我们先看看C#的array是怎么变成lua的userdata的:- public void Push(Array array)
- {
- if (array == null)
- {
- LuaPushNil();
- }
- else
- {
- PushUserData(array, ArrayMetatable);
- }
- }
复制代码 这里的ArrayMetatable就是lua层reference table的reference,在前面BeginClass时已经缓存在C#了。- void PushUserData(object o, int reference)
- {
- int index;
- if (translator.Getudata(o, out index))
- {
- if (LuaDLL.tolua_pushudata(L, index))
- {
- return;
- }
- translator.Destroyudata(index);
- }
- index = translator.AddObject(o);
- LuaDLL.tolua_pushnewudata(L, reference, index);
- }
复制代码 第5-13行,首先查找C#缓存objectsBackMap,这个缓存记录了push到lua层的C# object与lua userdata存放index的映射关系。如果查找到了,会拿查到的index到lua层进行查验:- LUALIB_API bool tolua_pushudata(lua_State *L, int index)
- {
- lua_getref(L, LUA_RIDX_UBOX); // stack: ubox
- lua_rawgeti(L, -1, index); // stack: ubox, obj
- if (!lua_isnil(L, -1))
- {
- lua_remove(L, -2); // stack: obj
- return true;
- }
- lua_pop(L, 2);
- return false;
- }
复制代码 LUA_RIDX_UBOX保留了C#层push进来的userdata,我们按照传进来的index查找对应的userdata,如果查找到了,就将其压入栈顶,返回true;如果查找掉败则返回false。
回到C#层,如果lua层的查验成功,那么什么都不用做,因为userdata已经在lua栈顶了,直接返回即可;如果掉败了,说明这个userdata在lua层并不存在,C#缓存已经掉效,需要将其断根。第15行就是在C#层为object生成一个新的index,使用这个新的index在lua层生成一个新的userdata:- LUALIB_API void tolua_pushnewudata(lua_State *L, int metaRef, int index)
- {
- lua_getref(L, LUA_RIDX_UBOX);
- tolua_newudata(L, index);
- lua_getref(L, metaRef);
- lua_setmetatable(L, -2);
- lua_pushvalue(L, -1);
- lua_rawseti(L, -3, index);
- lua_remove(L, -2);
- }
复制代码 lua层先是创建了一个userdata,并设置它的值为index,然后设置userdata的metatable为我们之前注册class生成的reference table,最后把这个userdata塞到LUA_RIDX_UBOX缓存,对应的key就是index。同样地,lua栈顶保留了一份userdata。
那么,此刻lua代码中的array已经是一个userdata了,往下看首先会调用到array.Length,这会触发userdta的metatable,也就是reference table,它的__index元方式,这个是在之前tolua_beginclass的tolua_setindex函数中设置的:- LUALIB_API void tolua_setindex(lua_State *L)
- {
- lua_pushstring(L, ”__index”);
- lua_pushcfunction(L, class_index_event);
- lua_rawset(L, -3);
- }
复制代码 那么此时就会触发调用这个class_index_event函数了,这个函数按照调用方是userdata还是table,以及要访谒的key是哪种类型,分袂做了分歧措置,这里我们只看相关的部门:- static int class_index_event(lua_State *L)
- {
- int t = lua_type(L, 1);
- if (t == LUA_TUSERDATA)
- {
- lua_getfenv(L,1);
- if (!lua_rawequal(L, -1, TOLUA_NOPEER)) // stack: t k env
- {
- ...
- }
- lua_settop(L,2);
- lua_pushvalue(L, 1); // stack: obj key obj
- while (lua_getmetatable(L, -1) != 0)
- {
- lua_remove(L, -2); // stack: obj key mt
- if (lua_isnumber(L,2)) // check if key is a numeric value
- {
- ...
- }
- else
- {
- lua_pushvalue(L, 2); // stack: obj key mt key
- lua_rawget(L, -2); // stack: obj key mt value
- if (!lua_isnil(L, -1))
- {
- return 1;
- }
- lua_pop(L, 1);
- lua_pushlightuserdata(L, &gettag);
- lua_rawget(L, -2); //stack: obj key mt tget
- if (lua_istable(L, -1))
- {
- lua_pushvalue(L, 2); //stack: obj key mt tget key
- lua_rawget(L, -2); //stack: obj key mt tget value
- if (lua_isfunction(L, -1))
- {
- lua_pushvalue(L, 1);
- lua_call(L, 1, 1);
- return 1;
- }
- }
- }
- lua_settop(L, 3);
- }
- lua_settop(L, 2);
- int *udata = (int*)lua_touserdata(L, 1);
- if (*udata == LUA_NULL_USERDATA)
- {
- return luaL_error(L, ”attemp to index %s on a nil value”, lua_tostring(L, 2));
- }
- if (toluaflags & FLAG_INDEX_ERROR)
- {
- return luaL_error(L, ”field or property %s does not exist”, lua_tostring(L, 2));
- }
- }
- else if(t == LUA_TTABLE)
- {
- ...
- }
- lua_pushnil(L);
- return 1;
- }
复制代码 第7-12行获取了绑定在userdata上的env table,它用来实现lua层担任扩展C#对象的机制,这个我们后面再说;第17行开始是一个循环,不竭地获取metatable,其含义就是如果当前类的reference table没有找到对应的元素,还会往基类的reference table中查找。第21行判断索引的key是否为number,我们这里的key是Length,是一个字符串,因此实际的正片要从第27行开始。首先查找当前reference table是否包含索引的key,如果包含直接取出,这里对应的就是先前注册过的各种方式;而Length是一个get属性,因此还要继续在reference table的get table中查找,找到对应的返回get属性的函数,直接调用得到成果。如果循环结束时还未查找成功,则说明要么userdata不合法,要么索引的key压根没注册,需要按照分歧的情况进行报错。
既然找到了Length对应的get函数,我们回到C#层看看它是怎么实现的:- static int GetLength(IntPtr L)
- {
- try
- {
- ToLua.CheckArgsCount(L, 2);
- System.Array obj = (System.Array)ToLua.CheckObject<Array>(L, 1);
- int arg0 = (int)LuaDLL.luaL_checknumber(L, 2);
- int o = obj.GetLength(arg0);
- LuaDLL.lua_pushinteger(L, o);
- return 1;
- }
- catch (Exception e)
- {
- return LuaDLL.toluaL_exception(L, e);
- }
- }
复制代码 函数的重点在第6-7行,就是要把lua栈上的数据转换成正确的C#类型,如何把userdata转换成本来的C# object呢?这点到此刻其实已经很明了了,lua层的userdata记录了它对应C#层缓存的index,我们只要通过这个index,反查C#缓存,就能取出缓存的object。
与array.Length类似,下标访谒array时,索引的key变成了number:- if (lua_isnumber(L,2)) // check if key is a numeric value
- {
- lua_pushstring(L,”.geti”);
- lua_rawget(L,-2); // stack: obj key mt func
- if (lua_isfunction(L,-1))
- {
- lua_pushvalue(L,1);
- lua_pushvalue(L,2);
- lua_call(L,2,1);
- return 1;
- }
- }
复制代码 可以看到这里有个trick,在当时注册下标操作时,注册的其实是一个.geti对应的函数,这个函数负责接受object和index参数,返回object[index],将其压入lua栈中。- static int get_Item(IntPtr L)
- {
- try
- {
- Array obj = ToLua.ToObject(L, 1) as Array;
- if (obj == null)
- {
- throw new LuaException(”trying to index an invalid object reference”);
- }
- int index = (int)LuaDLL.lua_tointeger(L, 2);
- if (index >= obj.Length)
- {
- throw new LuaException(”array index out of bounds: ” + index + ” ” + obj.Length);
- }
- Type t = obj.GetType().GetElementType();
- if (t.IsValueType)
- {
- ...
- }
- object val = obj.GetValue(index);
- ToLua.Push(L, val);
- return 1;
- }
- catch (Exception e)
- {
- return LuaDLL.toluaL_exception(L, e);
- }
- }
复制代码 总结一下,要想在lua层访谒某个C#类的方式或属性,需要提前对这个类进行注册,注册就是在lua层生成一个class table和reference table,class table用于lua层直接通过类而不是对象来访谒C#方式或属性,reference table中包含了各种key对应的C#函数,以及get/set table,分袂包含对应的C#属性,C#的下标访谒操作则是通过特殊的.geti/.seti函数封装的。reference table是class table的metatable,如果该类有基类,还会把基类的reference table设置为当前reference table的metatable,用于实现基类查找。每一个push到lua层的C#对象,在lua层是以userdata的形式存在,userdata的值是其C#对象在C#缓存中的index,每个userdata的metatable都是各自对应C#类型的reference table。
下一节我们将存眷一些特殊C#语法在lua层的实现,例如枚举Enum。
如果你感觉我的文章有辅佐,欢迎存眷我的微信公众号我是真的想做游戏啊 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|