gzfarmer 发表于 2024-7-15 18:35

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

上一节我们讨论了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))
            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))
            end

            local pos = array:BinarySearch(3)
            print('array BinarySearch: pos: '..pos..' value: '..array)

            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;
}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 = 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,将其压入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。

如果你感觉我的文章有辅佐,欢迎存眷我的微信公众号我是真的想做游戏啊
页: [1]
查看完整版本: tolua源码分析(四)lua调用C#函数机制的实现