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

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[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[i]就能取出对应下标的元素;还可以直接使用array的get/set属性,例如Length。运行结果如下图:

tolua源码分析(四)lua访问C

那么,这一切是如何完成的呢?让我们回忆一下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)];
}   

BeginModuleEndModule是一组配对函数,用来将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;
    }
}

在这里插入图片描述

接下来,调用的是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栈如下图所示:

在这里插入图片描述

第13-15行设置了这个table的__index域,显然这个table会被拿来用作metatable使用,这里的module_index_event函数我们等用到的时候再展开说。接下来17-18行连续两个pushstring,显然是为了记录这个table的name,此时的lua栈如图所示:

tolua源码分析(四)lua_pushstring

第19行来了个pushmodule函数,这个函数使用了一个buffer来缓存当前注册过程中已经注册过的namespace,这样就能够通过拼接得到当前namespace的完整名称,这里namespace System就已经是完整名称了,因此push进lua栈的还是System,此时的lua栈如图所示:

tolua源码分析(四)pushmodule

第20行设置了这个table的.name域为System,它表示对应C# namespace的完整名称。紧接着第21行又把table复制了一份,压到了栈顶:

tolua源码分析(四)lua_pushvalue

第22行就是把namespace信息保存到_G了,简单来说就是_G["System"] = new table,最后第24-25行,就是把该table的metatable设置为它自身,函数执行结束时,会在栈上留下一份该table,这样做的目的是让该namespace下的class及namespace都能够关联到table上。

tolua源码分析(四)tolua_beginmodule

总的来说,tolua_beginmodule做的事情,就是创建了一个table,设置table的__index.name还有metatable,其中.name是table对应C# namespace的全称,metatable为它自身。

接下来就是重头戏System_ArrayWrap.RegisterSystem_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的注册也需要一组配对方法BeginClassEndClass,其中属性相关的注册是使用RegVar方法,方法和索引操作的注册是使用RegFunction方法。那么首先从BeginClass看起,它接受两个type参数,分别表示当前注册的类型与其基类,这里System.ObjectArray的基类:

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

第6行的_addtoloaded函数,顾名思义,就是将class table保存到package.loaded里,用lua代码描述就是:package.loaded["System.Array"] = table。第8-17行,根据当前类型的reference是否存在,如果存在直接调用lua_getref取出对应的table,否则就再新建一个table,作为该类型的reference table,同时得到reference,此时lua栈如图所示:

tolua源码分析(四)tolua_beginclass2

第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_setindextolua_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源码分析(四)tolua_beginclass3

二是把class table塞到namespace中,这里就是_G.System["Array"] = class。自此class的注册过程彻底结束,lua栈也恢复到了注册前的模样:

tolua源码分析(四)tolua_beginclass4

我们之前提到过,在注册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_beginclasstolua_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[i]时,索引的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。

如果你觉得我的文章有帮助,欢迎关注我的微信公众号 我是真的想做游戏啊

猜你喜欢

转载自blog.csdn.net/weixin_45776473/article/details/130042849
今日推荐