找回密码
 立即注册
查看: 186|回复: 0

tolua源码分析(四)lua调用C#函数机制的实现

[复制链接]
发表于 2024-7-15 18:35 | 显示全部楼层 |阅读模式
上一节我们讨论了C#是如何访谒lua变量的,此次我们将研究lua是如何访谒到C#函数的。同样也是来看下官方的例子,example 08:
  1. string script =
  2.     @”
  3.         function TestArray(array)
  4.             local len = array.Length
  5.             for i = 0, len - 1 do
  6.                 print('Array: '..tostring(array[i]))
  7.             end
  8.             local iter = array:GetEnumerator()
  9.             while iter:MoveNext() do
  10.                 print('iter: '..iter.Current)
  11.             end
  12.             local t = array:ToTable()               
  13.             for i = 1, #t do
  14.                 print('table: '.. tostring(t[i]))
  15.             end
  16.             local pos = array:BinarySearch(3)
  17.             print('array BinarySearch: pos: '..pos..' value: '..array[pos])
  18.             pos = array:IndexOf(4)
  19.             print('array indexof bbb pos is: '..pos)
  20.             return 1, '123', true
  21.         end            
  22.     ”;
  23. LuaState lua = null;
  24. LuaFunction func = null;
  25. new LuaResLoader();
  26. lua = new LuaState();
  27. lua.Start();
  28. lua.DoString(script, ”AccessingArray.cs”);
  29. int[] array = { 1, 2, 3, 4, 5 };
  30. func = lua.GetFunction(”TestArray”);
  31. func.BeginPCall();
  32. func.Push(array);
  33. func.PCall();
  34. func.EndPCall();
复制代码
这个例子中,lua代码的TestArray函数接收一个array参数,它是来自C#的数组。我们仿佛就在C#层一样,可以直接调用array的方式,例如GetEnumerator;还可以直接使用下标访谒,array就能取出对应下标的元素;还可以直接使用array的get/set属性,例如Length。运行成果如下图:



tolua源码分析(四)例子运行成果

那么,这一切是如何完成的呢?让我们回忆一下tolua的初始化流程,在C#的LuaState类的构造函数中,有一个OpenBaseLibs的调用,它包含了一些最基本C#类的注册,此中就有我们这里要用到的System.Array。这里只截取了与System.Array相关的代码:
  1. void OpenBaseLibs()
  2. {            
  3.     BeginModule(null);
  4.     BeginModule(”System”);
  5.     System_ArrayWrap.Register(this);
  6.     EndModule();//end System
  7.     EndModule(); //end global
  8.     ArrayMetatable = metaMap[typeof(System.Array)];
  9. }
复制代码
BeginModule和EndModule是一组配对函数,用来将C#的namespace注册给lua。BeginModule会调到C层的tolua_beginmodule函数,一开始我们的参数为null,暗示我们即将向全局的namespace中注册各种东西,也就是筹备往lua层的_G中塞东西,那么对应的tolua_beginmodule实现也非常简单,就是将lua层的_G筹备好,此时的lua栈如图所示:
  1. LUALIB_API bool tolua_beginmodule(lua_State *L, const char *name)
  2. {
  3.     if (name != NULL)
  4.     {
  5.         ...
  6.     }
  7.     else
  8.     {
  9.         lua_pushvalue(L, LUA_GLOBALSINDEX);
  10.         return true;
  11.     }
  12. }
复制代码



tolua源码分析(四)调用BeginModule(null)时的lua栈

接下来,调用的是BeginModule(”System”),此时对应的tolua_beginmodule实现会稍微复杂一些,我们将一步步地模拟栈操作,对当前的lua栈进行可视化:
  1. LUALIB_API bool tolua_beginmodule(lua_State *L, const char *name)
  2. {
  3.     if (name != NULL)
  4.     {
  5.         lua_pushstring(L, name);            //stack key
  6.         lua_rawget(L, -2);                  //stack value
  7.         if (lua_isnil(L, -1))  
  8.         {
  9.             lua_pop(L, 1);
  10.             lua_newtable(L);                //stack table
  11.             lua_pushstring(L, ”__index”);
  12.             lua_pushcfunction(L, module_index_event);
  13.             lua_rawset(L, -3);
  14.             lua_pushstring(L, name);        //stack table name         
  15.             lua_pushstring(L, ”.name”);     //stack table name ”.name”            
  16.             pushmodule(L, name);            //stack table name ”.name” module            
  17.             lua_rawset(L, -4);              //stack table name            
  18.             lua_pushvalue(L, -2);           //stack table name table
  19.             lua_rawset(L, -4);              //stack table
  20.             lua_pushvalue(L, -1);
  21.             lua_setmetatable(L, -2);
  22.             return true;
  23.         }
  24.         else if (lua_istable(L, -1))
  25.         {
  26.             ...
  27.         }
  28.     }
  29.     else
  30.     {
  31.         ...
  32.     }
  33. }
复制代码
第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层的方式,属性,以及下标访谒等操作:
  1. public static void Register(LuaState L)
  2. {
  3.     L.BeginClass(typeof(Array), typeof(System.Object));
  4.     L.RegFunction(”.geti”, get_Item);
  5.     L.RegFunction(”.seti”, set_Item);
  6.     L.RegFunction(”ToTable”, ToTable);
  7.     L.RegFunction(”GetLength”, GetLength);
  8.     L.RegFunction(”GetLongLength”, GetLongLength);
  9.     L.RegFunction(”GetLowerBound”, GetLowerBound);
  10.     L.RegFunction(”GetValue”, GetValue);
  11.     L.RegFunction(”SetValue”, SetValue);
  12.     L.RegFunction(”GetEnumerator”, GetEnumerator);
  13.     L.RegFunction(”GetUpperBound”, GetUpperBound);
  14.     L.RegFunction(”CreateInstance”, CreateInstance);
  15.     L.RegFunction(”BinarySearch”, BinarySearch);
  16.     L.RegFunction(”Clear”, Clear);
  17.     L.RegFunction(”Clone”, Clone);
  18.     L.RegFunction(”Copy”, Copy);
  19.     L.RegFunction(”IndexOf”, IndexOf);
  20.     L.RegFunction(”Initialize”, Initialize);
  21.     L.RegFunction(”LastIndexOf”, LastIndexOf);
  22.     L.RegFunction(”Reverse”, Reverse);
  23.     L.RegFunction(”Sort”, Sort);
  24.     L.RegFunction(”CopyTo”, CopyTo);
  25.     L.RegFunction(”ConstrainedCopy”, ConstrainedCopy);
  26.     L.RegFunction(”__tostring”, ToLua.op_ToString);
  27.     L.RegVar(”Length”, get_Length, null);
  28.     L.RegVar(”LongLength”, get_LongLength, null);
  29.     L.RegVar(”Rank”, get_Rank, null);
  30.     L.RegVar(”IsSynchronized”, get_IsSynchronized, null);
  31.     L.RegVar(”SyncRoot”, get_SyncRoot, null);
  32.     L.RegVar(”IsFixedSize”, get_IsFixedSize, null);
  33.     L.RegVar(”IsReadOnly”, get_IsReadOnly, null);
  34.     L.EndClass();
  35. }
复制代码
与namespace类似,class的注册也需要一组配对方式BeginClass和EndClass,此中属性相关的注册是使用RegVar方式,方式和索引操作的注册是使用RegFunction方式。那么首先从BeginClass看起,它接受两个type参数,分袂暗示当前注册的类型与其基类,这里System.Object是Array的基类:
  1. public int BeginClass(Type t, Type baseType, string name = null)
  2. {
  3.     if (beginCount == 0)
  4.     {
  5.         throw new LuaException(”must call BeginModule first”);
  6.     }
  7.     int baseMetaRef = 0;
  8.     int reference = 0;            
  9.     if (name == null)
  10.     {
  11.         name = GetToLuaTypeName(t);
  12.     }
  13.     if (baseType != null && !metaMap.TryGetValue(baseType, out baseMetaRef))
  14.     {
  15.         LuaCreateTable();
  16.         baseMetaRef = LuaRef(LuaIndexes.LUA_REGISTRYINDEX);               
  17.         BindTypeRef(baseMetaRef, baseType);
  18.     }
  19.     if (metaMap.TryGetValue(t, out reference))
  20.     {
  21.         LuaDLL.tolua_beginclass(L, name, baseMetaRef, reference);
  22.         RegFunction(”__gc”, Collect);
  23.     }
  24.     else
  25.     {
  26.         reference = LuaDLL.tolua_beginclass(L, name, baseMetaRef);
  27.         RegFunction(”__gc”, Collect);               
  28.         BindTypeRef(reference, t);
  29.     }
  30.     return reference;
  31. }
复制代码
函数首先会进行一次查抄,即第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层实现,相对也斗劲复杂:
  1. LUALIB_API int tolua_beginclass(lua_State *L, const char *name, int baseType, int ref)
  2. {
  3.     int reference = ref;
  4.     lua_pushstring(L, name);               
  5.     lua_newtable(L);      
  6.     _addtoloaded(L);
  7.     if (ref == LUA_REFNIL)        
  8.     {
  9.         lua_newtable(L);
  10.         lua_pushvalue(L, -1);
  11.         reference = luaL_ref(L, LUA_REGISTRYINDEX);
  12.     }
  13.     else
  14.     {
  15.         lua_getref(L, reference);   
  16.     }
  17.     if (baseType != 0)
  18.     {
  19.         lua_getref(L, baseType);        
  20.         lua_setmetatable(L, -2);
  21.     }
  22.     lua_pushlightuserdata(L, &tag);
  23.     lua_pushnumber(L, 1);
  24.     lua_rawset(L, -3);
  25.     lua_pushstring(L, ”.name”);
  26.     _pushfullname(L, -4);
  27.     lua_rawset(L, -3);
  28.     lua_pushstring(L, ”.ref”);
  29.     lua_pushinteger(L, reference);
  30.     lua_rawset(L, -3);
  31.     lua_pushstring(L, ”__call”);
  32.     lua_pushcfunction(L, class_new_event);
  33.     lua_rawset(L, -3);
  34.     tolua_setindex(L);
  35.     tolua_setnewindex(L);
  36.     return reference;
  37. }
复制代码
第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的实现:
  1. public void RegFunction(string name, LuaCSFunction func)
  2. {
  3.     IntPtr fn = Marshal.GetFunctionPointerForDelegate(func);
  4.     LuaDLL.tolua_function(L, name, fn);            
  5. }
复制代码
函数实现异常简单,主要逻辑在C层的tolua_function中:
  1. LUALIB_API void tolua_function(lua_State *L, const char *name, lua_CFunction fn)
  2. {
  3.     lua_pushstring(L, name);
  4.     tolua_pushcfunction(L, fn);
  5.     lua_rawset(L, -3);
  6. }
复制代码
也很简单,就是往栈顶里的table塞数据,而由前面的图可知,此时lua栈顶的table为该类型的reference table,用lua代码描述Register中的RegFunction操作,也就是:
  1. ref[”.geti”] = get_Item
  2. ref[”.seti”] = set_Item
  3. ref[”ToTable”] = ToTable
  4. ...
复制代码
下面再看下注册属性的RegVar实现:
  1. public void RegVar(string name, LuaCSFunction get, LuaCSFunction set)
  2. {            
  3.     IntPtr fget = IntPtr.Zero;
  4.     IntPtr fset = IntPtr.Zero;
  5.     if (get != null)
  6.     {
  7.         fget = Marshal.GetFunctionPointerForDelegate(get);
  8.     }
  9.     if (set != null)
  10.     {
  11.         fset = Marshal.GetFunctionPointerForDelegate(set);
  12.     }
  13.     LuaDLL.tolua_variable(L, name, fget, fset);
  14. }
复制代码
可以看到get和set是分隔的,与C#层保持一致,只有C#层的get/set属性存在,才会注册到lua层。来看下C层的tolua_variable:
  1. LUALIB_API void tolua_variable(lua_State *L, const char *name, lua_CFunction get, lua_CFunction set)
  2. {               
  3.     lua_pushlightuserdata(L, &gettag);
  4.     lua_rawget(L, -2);
  5.     if (!lua_istable(L, -1))
  6.     {
  7.         /* create .get table, leaving it at the top */
  8.         lua_pop(L, 1);
  9.         lua_newtable(L);        
  10.         lua_pushlightuserdata(L, &gettag);
  11.         lua_pushvalue(L, -2);
  12.         lua_rawset(L, -4);
  13.     }
  14.     lua_pushstring(L, name);
  15.     tolua_pushcfunction(L, get);
  16.     lua_rawset(L, -3);                  /* store variable */
  17.     lua_pop(L, 1);                      /* pop .get table */
  18.     /* set func */
  19.     if (set != NULL)
  20.     {        
  21.         lua_pushlightuserdata(L, &settag);
  22.         lua_rawget(L, -2);
  23.         if (!lua_istable(L, -1))
  24.         {
  25.             /* create .set table, leaving it at the top */
  26.             lua_pop(L, 1);
  27.             lua_newtable(L);            
  28.             lua_pushlightuserdata(L, &settag);
  29.             lua_pushvalue(L, -2);
  30.             lua_rawset(L, -4);
  31.         }
  32.         lua_pushstring(L, name);
  33.         tolua_pushcfunction(L, set);
  34.         lua_rawset(L, -3);                  /* store variable */
  35.         lua_pop(L, 1);                      /* pop .set table */
  36.     }
  37. }
复制代码
类似地,为了保证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了:
  1. LUALIB_API void tolua_endclass(lua_State *L)
  2. {
  3.     lua_setmetatable(L, -2);
  4.     lua_rawset(L, -3);            
  5. }
复制代码
函数做了两件事,一是把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的:
  1. public void Push(Array array)
  2. {
  3.     if (array == null)
  4.     {               
  5.         LuaPushNil();
  6.     }
  7.     else
  8.     {
  9.         PushUserData(array, ArrayMetatable);
  10.     }
  11. }
复制代码
这里的ArrayMetatable就是lua层reference table的reference,在前面BeginClass时已经缓存在C#了。
  1. void PushUserData(object o, int reference)
  2. {
  3.     int index;
  4.     if (translator.Getudata(o, out index))
  5.     {
  6.         if (LuaDLL.tolua_pushudata(L, index))
  7.         {
  8.             return;
  9.         }
  10.         translator.Destroyudata(index);
  11.     }
  12.     index = translator.AddObject(o);
  13.     LuaDLL.tolua_pushnewudata(L, reference, index);
  14. }
复制代码
第5-13行,首先查找C#缓存objectsBackMap,这个缓存记录了push到lua层的C# object与lua userdata存放index的映射关系。如果查找到了,会拿查到的index到lua层进行查验:
  1. LUALIB_API bool tolua_pushudata(lua_State *L, int index)
  2. {
  3.     lua_getref(L, LUA_RIDX_UBOX);           // stack: ubox
  4.     lua_rawgeti(L, -1, index);              // stack: ubox, obj
  5.     if (!lua_isnil(L, -1))
  6.     {
  7.         lua_remove(L, -2);                  // stack: obj
  8.         return true;
  9.     }
  10.     lua_pop(L, 2);
  11.     return false;
  12. }
复制代码
LUA_RIDX_UBOX保留了C#层push进来的userdata,我们按照传进来的index查找对应的userdata,如果查找到了,就将其压入栈顶,返回true;如果查找掉败则返回false。
回到C#层,如果lua层的查验成功,那么什么都不用做,因为userdata已经在lua栈顶了,直接返回即可;如果掉败了,说明这个userdata在lua层并不存在,C#缓存已经掉效,需要将其断根。第15行就是在C#层为object生成一个新的index,使用这个新的index在lua层生成一个新的userdata:
  1. LUALIB_API void tolua_pushnewudata(lua_State *L, int metaRef, int index)
  2. {
  3.     lua_getref(L, LUA_RIDX_UBOX);
  4.     tolua_newudata(L, index);
  5.     lua_getref(L, metaRef);
  6.     lua_setmetatable(L, -2);
  7.     lua_pushvalue(L, -1);
  8.     lua_rawseti(L, -3, index);
  9.     lua_remove(L, -2);  
  10. }
复制代码
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函数中设置的:
  1. LUALIB_API void tolua_setindex(lua_State *L)
  2. {
  3.     lua_pushstring(L, ”__index”);
  4.     lua_pushcfunction(L, class_index_event);
  5.     lua_rawset(L, -3);
  6. }
复制代码
那么此时就会触发调用这个class_index_event函数了,这个函数按照调用方是userdata还是table,以及要访谒的key是哪种类型,分袂做了分歧措置,这里我们只看相关的部门:
  1. static int class_index_event(lua_State *L)
  2. {
  3.     int t = lua_type(L, 1);
  4.     if (t == LUA_TUSERDATA)
  5.     {      
  6.         lua_getfenv(L,1);
  7.         if (!lua_rawequal(L, -1, TOLUA_NOPEER))     // stack: t k env
  8.         {
  9.             ...
  10.         }
  11.         lua_settop(L,2);                                                        
  12.         lua_pushvalue(L, 1);                        // stack: obj key obj   
  13.         while (lua_getmetatable(L, -1) != 0)
  14.         {           
  15.             lua_remove(L, -2);                      // stack: obj key mt
  16.             if (lua_isnumber(L,2))                  // check if key is a numeric value
  17.             {           
  18.                 ...
  19.             }
  20.             else
  21.             {
  22.                 lua_pushvalue(L, 2);                // stack: obj key mt key
  23.                 lua_rawget(L, -2);                  // stack: obj key mt value        
  24.                 if (!lua_isnil(L, -1))
  25.                 {
  26.                     return 1;
  27.                 }
  28.                 lua_pop(L, 1);
  29.                 lua_pushlightuserdata(L, &gettag);         
  30.                 lua_rawget(L, -2);                  //stack: obj key mt tget
  31.                 if (lua_istable(L, -1))
  32.                 {
  33.                     lua_pushvalue(L, 2);            //stack: obj key mt tget key
  34.                     lua_rawget(L, -2);              //stack: obj key mt tget value
  35.                     if (lua_isfunction(L, -1))
  36.                     {
  37.                         lua_pushvalue(L, 1);
  38.                         lua_call(L, 1, 1);
  39.                         return 1;
  40.                     }                    
  41.                 }
  42.             }
  43.             lua_settop(L, 3);
  44.         }
  45.         lua_settop(L, 2);
  46.         int *udata = (int*)lua_touserdata(L, 1);
  47.         if (*udata == LUA_NULL_USERDATA)
  48.         {
  49.             return luaL_error(L, ”attemp to index %s on a nil value”, lua_tostring(L, 2));   
  50.         }
  51.         if (toluaflags & FLAG_INDEX_ERROR)
  52.         {
  53.             return luaL_error(L, ”field or property %s does not exist”, lua_tostring(L, 2));
  54.         }        
  55.     }
  56.     else if(t == LUA_TTABLE)
  57.     {
  58.         ...
  59.     }
  60.     lua_pushnil(L);
  61.     return 1;
  62. }
复制代码
第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#层看看它是怎么实现的:
  1. static int GetLength(IntPtr L)
  2. {
  3.     try
  4.     {
  5.         ToLua.CheckArgsCount(L, 2);
  6.         System.Array obj = (System.Array)ToLua.CheckObject<Array>(L, 1);
  7.         int arg0 = (int)LuaDLL.luaL_checknumber(L, 2);
  8.         int o = obj.GetLength(arg0);
  9.         LuaDLL.lua_pushinteger(L, o);
  10.         return 1;
  11.     }
  12.     catch (Exception e)
  13.     {
  14.         return LuaDLL.toluaL_exception(L, e);
  15.     }
  16. }
复制代码
函数的重点在第6-7行,就是要把lua栈上的数据转换成正确的C#类型,如何把userdata转换成本来的C# object呢?这点到此刻其实已经很明了了,lua层的userdata记录了它对应C#层缓存的index,我们只要通过这个index,反查C#缓存,就能取出缓存的object。
与array.Length类似,下标访谒array时,索引的key变成了number:
  1. if (lua_isnumber(L,2))                  // check if key is a numeric value
  2. {           
  3.     lua_pushstring(L,”.geti”);
  4.     lua_rawget(L,-2);                   // stack: obj key mt func
  5.     if (lua_isfunction(L,-1))
  6.     {
  7.         lua_pushvalue(L,1);
  8.         lua_pushvalue(L,2);
  9.         lua_call(L,2,1);
  10.         return 1;
  11.     }
  12. }
复制代码
可以看到这里有个trick,在当时注册下标操作时,注册的其实是一个.geti对应的函数,这个函数负责接受object和index参数,返回object[index],将其压入lua栈中。
  1. static int get_Item(IntPtr L)
  2. {
  3.     try
  4.     {
  5.         Array obj = ToLua.ToObject(L, 1) as Array;
  6.         if (obj == null)
  7.         {
  8.             throw new LuaException(”trying to index an invalid object reference”);               
  9.         }
  10.         int index = (int)LuaDLL.lua_tointeger(L, 2);
  11.         if (index >= obj.Length)
  12.         {
  13.             throw new LuaException(”array index out of bounds: ” + index + ” ” + obj.Length);               
  14.         }
  15.         Type t = obj.GetType().GetElementType();
  16.         if (t.IsValueType)
  17.         {
  18.             ...
  19.         }            
  20.         object val = obj.GetValue(index);
  21.         ToLua.Push(L, val);
  22.         return 1;
  23.     }
  24.     catch (Exception e)
  25.     {
  26.         return LuaDLL.toluaL_exception(L, e);
  27.     }
  28. }
复制代码
总结一下,要想在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。

如果你感觉我的文章有辅佐,欢迎存眷我的微信公众号我是真的想做游戏啊

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-21 20:31 , Processed in 0.103469 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表