主頁 > .NET開發 > 深入xLua實作原理之Lua如何呼叫C#

深入xLua實作原理之Lua如何呼叫C#

2021-09-18 20:28:21 .NET開發

xLua是騰訊的一個開源專案,為Unity、 .Net、 Mono等C#環境增加Lua腳本編程的能力,本文主要是探討xLua下Lua呼叫C#的實作原理,

Lua與C#資料通信機制

無論是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);

傳遞C#物件到Lua

對于bool,int這樣簡單的值型別可以直接通過C API傳遞,但對于C#物件就不同了,Lua這邊沒有能與之對應的型別,因此傳遞到Lua的只是C#物件的一個索引,具體實作請看下面的代碼

// ObjectTranslator.cs
public void Push(RealStatePtr L, object o)
{
    // ...
    int index = -1;
    Type type = o.GetType();
#if !UNITY_WSA || UNITY_EDITOR
    bool is_enum = type.IsEnum;
    bool is_valuetype = type.IsValueType;
#else
    bool is_enum = type.GetTypeInfo().IsEnum;
    bool is_valuetype = type.GetTypeInfo().IsValueType;
#endif
    bool needcache = !is_valuetype || is_enum;  // 如果是參考或列舉,會進行快取
    if (needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index)))  // 如果有快取
    {
        if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1)  
        {
            return;
        }
        //這里實在太經典了,weaktable先洗掉,然后GC會延遲呼叫,當index會回圈利用的時候,不注釋這行將會導致重復釋放
        //collectObject(index);
    }

    bool is_first;
    int type_id = getTypeId(L, type, out is_first);

    //如果一個type的定義含本身靜態readonly實體時,getTypeId會push一個實體,這時候應該用這個實體
    if (is_first && needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index))) 
    {
        if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1)   
        {
            return;
        }
    }
    // C#側進行快取
    index = addObject(o, is_valuetype, is_enum);
    // 將代表物件的索引push到lua
    LuaAPI.xlua_pushcsobj(L, index, type_id, needcache, cacheRef);
}

代碼中的兩個if陳述句主要是對快取的判斷,如果要傳遞的物件已經被快取過了就直接使用快取的,如果這個物件是被第一次傳遞,則進行以下兩步操作

  1. 通過addObject將物件快取在objects物件池中,并得到一個索引(通過這個索引可以獲取到該物件)

    // ObjectTranslator.cs
    int addObject(object obj, bool is_valuetype, bool is_enum)
    {
        int index = objects.Add(obj);
        if (is_enum)
        {
            enumMap[obj] = index;
        }
        else if (!is_valuetype)
        {
            reverseMap[obj] = index;
        }
        
        return index;
    }
    
  2. 通過xlua_pushcsobj將代表物件的索引傳遞到Lua,

    引數key表示代表物件的索引,引數meta_ref表示代表物件型別的表的索引,它的值是通過getTypeId函式獲得的,后面會詳細講到,引數need_cache表示是否需要在Lua側進行快取,引數cache_ref表示Lua側快取表的索引

    // xlua.c
    LUA_API void xlua_pushcsobj(lua_State *L, int key, int meta_ref, int need_cache, int cache_ref) {
        int* pointer = (int*)lua_newuserdata(L, sizeof(int));
        *pointer = key;
        
        if (need_cache) cacheud(L, key, cache_ref);  // Lua側快取
    
        lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref);
    
        lua_setmetatable(L, -2);  // 為userdata設定元表
    }
    
    // 將 key = userdata 存入快取表
    static void cacheud(lua_State *L, int key, int cache_ref) {
        lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref);
        lua_pushvalue(L, -2);
        lua_rawseti(L, -2, key);
        lua_pop(L, 1);
    }
    

    xlua_pushcsobj的主要邏輯是,代表物件的索引被push到Lua后,Lua會為其創建一個userdata,并將這個userdata指向物件索引,如果需要快取則將userdata保存到快取表中, 最后為userdata設定了元表,也就是說,C#物件在Lua這邊對應的就是一個userdata,利用物件索引保持與C#物件的聯系,

注冊C#型別資訊到Lua

為userdata(特指C#物件在Lua這邊對應的代理userdata,后面再出現的userdata也是同樣的含義,就不再贅述了)設定的元表,表示的實際是物件的型別資訊,在將C#物件傳遞到Lua以后,還需要告知Lua該物件的型別資訊,比如物件型別有哪些成員方法,屬性或是靜態方法等,將這些都注冊到Lua后,Lua才能正確的呼叫,這個元表是通過getTypeId函式生成的

// ObjectTranslator.cs
internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN)
{
    int type_id;
    is_first = false;
    if (!typeIdMap.TryGetValue(type, out type_id)) // no reference
    {
        // ...
        is_first = true;
        Type alias_type = null;
        aliasCfg.TryGetValue(type, out alias_type);
        LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);

        if (LuaAPI.lua_isnil(L, -1)) //no meta yet, try to use reflection meta
        {
            LuaAPI.lua_pop(L, 1);

            if (TryDelayWrapLoader(L, alias_type == null ? type : alias_type))
            {
                LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);
            }
            else
            {
                throw new Exception("Fatal: can not load metatable of type:" + type);
            }
        }

        //回圈依賴,自身依賴自己的class,比如有個自身型別的靜態readonly物件,
        if (typeIdMap.TryGetValue(type, out type_id))
        {
            LuaAPI.lua_pop(L, 1);
        }
        else
        {
            // ...
            LuaAPI.lua_pushvalue(L, -1);
            type_id = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);  // 將元表添加到注冊表中
            LuaAPI.lua_pushnumber(L, type_id);
            LuaAPI.xlua_rawseti(L, -2, 1);   // 元表[1] = type_id
            LuaAPI.lua_pop(L, 1);

            if (type.IsValueType())
            {
                typeMap.Add(type_id, type);
            }

            typeIdMap.Add(type, type_id);
        }
    }
    return type_id;
}

函式主要邏輯是以類的名稱為key通過luaL_getmetatable獲取類對應的元表,如果獲取不到,則通過TryDelayWrapLoader函式生成,然后呼叫luaL_ref將獲取到的元表添加到Lua注冊表中,并回傳type_id,type_id表示的就是元表在Lua注冊表中的索引,通過這個索引可以在Lua注冊表中取回元表,前面提到的xlua_pushcsobj函式就是利用type_id即meta_ref,獲取到元表,然后為userdata設定的元表,

下面來看元表具體是怎樣生成的

// ObjectTranslator.cs
public bool TryDelayWrapLoader(RealStatePtr L, Type type)
{
    // ...
    LuaAPI.luaL_newmetatable(L, type.FullName); //先建一個metatable,因為加載程序可能會需要用到
    LuaAPI.lua_pop(L, 1);

    Action<RealStatePtr> loader;
    int top = LuaAPI.lua_gettop(L);
    if (delayWrap.TryGetValue(type, out loader))  // 如果有預先注冊的型別元表生成器,則直接使用
    {
        delayWrap.Remove(type);
        loader(L);
    }
    else
    {
#if !GEN_CODE_MINIMIZE && !ENABLE_IL2CPP && (UNITY_EDITOR || XLUA_GENERAL) && !FORCE_REFLECTION && !NET_STANDARD_2_0
        if (!DelegateBridge.Gen_Flag && !type.IsEnum() && !typeof(Delegate).IsAssignableFrom(type) && Utils.IsPublic(type))
        {
            Type wrap = ce.EmitTypeWrap(type);
            MethodInfo method = wrap.GetMethod("__Register", BindingFlags.Static | BindingFlags.Public);
            method.Invoke(null, new object[] { L });
        }
        else
        {
            Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
        }
#else
        Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
#endif
        // ...
    }
    if (top != LuaAPI.lua_gettop(L))
    {
        throw new Exception("top change, before:" + top + ", after:" + LuaAPI.lua_gettop(L));
    }

    foreach (var nested_type in type.GetNestedTypes(BindingFlags.Public))
    {
        if (nested_type.IsGenericTypeDefinition())  // 過濾泛型型別定義
        {
            continue;
        }
        GetTypeId(L, nested_type);
    }
    
    return true;
}

TryDelayWrapLoader主要用來處理兩種情況

  1. 通過delayWrap判斷,是否有為該類生成代碼,如果有,直接使用生成函式進行填充元表(loader方法),在xLua的生成代碼中有一個XLuaGenAutoRegister.cs檔案,在這個檔案中會為對應的類注冊初始化器,而這個初始化器負責將類對應的元表生成函式添加到delayWrap中,
    // XLuaGenAutoRegister.cs
    public class XLua_Gen_Initer_Register__
    {
        static void wrapInit0(LuaEnv luaenv, ObjectTranslator translator)
        {
            // ...
            translator.DelayWrapLoader(typeof(TestXLua), TestXLuaWrap.__Register);  // 將型別對應的元表填充函式__Register添加到delayWrap中
            // ...
        }
        
        static void Init(LuaEnv luaenv, ObjectTranslator translator)
        {
            wrapInit0(luaenv, translator);
            translator.AddInterfaceBridgeCreator(typeof(System.Collections.IEnumerator), SystemCollectionsIEnumeratorBridge.__Create);
        }
        
        static XLua_Gen_Initer_Register__()
        {
    	    XLua.LuaEnv.AddIniter(Init);  // 注冊初始化器
    	}
    }
    
  2. 如果沒有生成代碼,通過反射填充元表(ReflectionWrap方法)

使用生成函式填充元表

以LuaCallCSharp修飾的TestXLua類為例來查看生成函式是如何生成的

// TestXLua.cs
[LuaCallCSharp]
public class TestXLua
{
    public string Name;
    public void Test1(int a){
    }
    public static void Test2(int a, bool b, string c)
    {
    }
}

Generate Code之后生成的TestXLuaWrap.cs如下所示

public class TestXLuaWrap 
{
    public static void __Register(RealStatePtr L)
    {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        System.Type type = typeof(TestXLua);
        Utils.BeginObjectRegister(type, L, translator, 0, 1, 1, 1);
        Utils.RegisterFunc(L, Utils.METHOD_IDX, "Test1", _m_Test1);
        Utils.RegisterFunc(L, Utils.GETTER_IDX, "Name", _g_get_Name);
        Utils.RegisterFunc(L, Utils.SETTER_IDX, "Name", _s_set_Name);
        Utils.EndObjectRegister(type, L, translator, null, null,
            null, null, null);
        Utils.BeginClassRegister(type, L, __CreateInstance, 2, 0, 0);
        Utils.RegisterFunc(L, Utils.CLS_IDX, "Test2", _m_Test2_xlua_st_);
        Utils.EndClassRegister(type, L, translator);
    }
    
    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int __CreateInstance(RealStatePtr L)
    {
        try {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
            if(LuaAPI.lua_gettop(L) == 1)
            {
                TestXLua gen_ret = new TestXLua();
                translator.Push(L, gen_ret);
                return 1;
            }
        }
        catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
        return LuaAPI.luaL_error(L, "invalid arguments to TestXLua constructor!");
        
    }

    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int _m_Test1(RealStatePtr L)
    {
        try {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
            TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
            {
                int _a = LuaAPI.xlua_tointeger(L, 2);
                gen_to_be_invoked.Test1( _a );
                return 0;
            }
        } catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
    }
    
    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int _m_Test2_xlua_st_(RealStatePtr L)
    {
        try {
            {
                int _a = LuaAPI.xlua_tointeger(L, 1);
                bool _b = LuaAPI.lua_toboolean(L, 2);
                string _c = LuaAPI.lua_tostring(L, 3);
                TestXLua.Test2( _a, _b, _c );
                return 0;
            }
        } catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
    }
    
    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int _g_get_Name(RealStatePtr L)
    {
        try {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        
            TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
            LuaAPI.lua_pushstring(L, gen_to_be_invoked.Name);
        } catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
        return 1;
    }
    
    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int _s_set_Name(RealStatePtr L)
    {
        try {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        
            TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
            gen_to_be_invoked.Name = LuaAPI.lua_tostring(L, 2);
        
        } catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
        return 0;
    }
}

生成函式__Register主要是這樣一個框架

  1. Utils.BeginObjectRegister,在對類的非靜態值(例如成員變數,成員方法等)進行注冊前做一些準備作業,主要是為元表添加__gc和__tostring元方法,以及準備好method表、getter表、setter表,后面呼叫RegisterFunc時,可以選擇插入到對應的表中

    // Utils.cs
    public static void BeginObjectRegister(Type type, RealStatePtr L, ObjectTranslator translator, int meta_count, int method_count, int getter_count,
        int setter_count, int type_id = -1)
    {
        if (type == null)
        {
            if (type_id == -1) throw new Exception("Fatal: must provide a type of type_id");
            LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, type_id);
        }
        else
        {
            LuaAPI.luaL_getmetatable(L, type.FullName);
            // 如果type.FullName對應的元表是空,則創建一個新的元表,并設定到注冊表中
            if (LuaAPI.lua_isnil(L, -1))
            {
                LuaAPI.lua_pop(L, 1);
                LuaAPI.luaL_newmetatable(L, type.FullName);
            }
        }
        LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag());
        LuaAPI.lua_pushnumber(L, 1);
        LuaAPI.lua_rawset(L, -3);  // 為元表設定標志
    
        if ((type == null || !translator.HasCustomOp(type)) && type != typeof(decimal))
        {
            LuaAPI.xlua_pushasciistring(L, "__gc");
            LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
            LuaAPI.lua_rawset(L, -3);  // 為元表設定__gc方法
        }
    
        LuaAPI.xlua_pushasciistring(L, "__tostring");
        LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);
        LuaAPI.lua_rawset(L, -3);  // 為元表設定__tostring方法
    
        if (method_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_createtable(L, 0, method_count);  // 創建method表
        }
    
        if (getter_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_createtable(L, 0, getter_count);  // 創建getter表
        }
    
        if (setter_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_createtable(L, 0, setter_count);  // 創建setter表
        }
    }
    
  2. 多個Utils.RegisterFunc,將類的每個非靜態值對應的包裹方法注冊到不同的Lua表中,包裹方法是Generate Code時動態生成的,對于類的屬性會生成兩個包裹方法,分別是get和set包裹方法,

    例如成員方法Test1對應的包裹方法是_m_Test1,并被注冊到了method表中,Name變數的_g_get_Name包裹方法被注冊到getter表,而_s_set_Name包裹方法被注冊到setter表,這個包裹方法只是對原來方法的一層包裹,呼叫這個包裹方法本質上就是呼叫原來的方法,至于為什么需要生成包裹方法,后面會再講到

    // Utils.cs RegisterFunc根據不同的宏定義會有不同的版本,但大同小異
    public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func)
    {
        idx = abs_idx(LuaAPI.lua_gettop(L), idx);
        LuaAPI.xlua_pushasciistring(L, name);
        LuaAPI.lua_pushstdcallcfunction(L, func);
        LuaAPI.lua_rawset(L, idx);  // 將idx指向的表中添加鍵值對 name = func
    }
    
  3. Utils.EndObjectRegister,結束對類的非靜態值的注冊,主要邏輯是為元表生成__index元方法和__newindex元方法,這也是Lua呼叫C#的核心所在

    // Utils.cs
    public static void EndObjectRegister(Type type, RealStatePtr L, ObjectTranslator translator, LuaCSFunction csIndexer,
        LuaCSFunction csNewIndexer, Type base_type, LuaCSFunction arrayIndexer, LuaCSFunction arrayNewIndexer)
    {
        int top = LuaAPI.lua_gettop(L);
        int meta_idx = abs_idx(top, OBJ_META_IDX);
        int method_idx = abs_idx(top, METHOD_IDX);
        int getter_idx = abs_idx(top, GETTER_IDX);
        int setter_idx = abs_idx(top, SETTER_IDX);
    
        //begin index gen
        LuaAPI.xlua_pushasciistring(L, "__index");
        LuaAPI.lua_pushvalue(L, method_idx);  // 1. 壓入methods表
        LuaAPI.lua_pushvalue(L, getter_idx);  // 2. 壓入getters表
    
        if (csIndexer == null)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            // ...
            LuaAPI.lua_pushstdcallcfunction(L, csIndexer);  // 3. 壓入csindexer
            // ...
        }
    
        translator.Push(L, type == null ? base_type : type.BaseType());  // 4. 壓入base
    
        LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);  // 5. 壓入indexfuncs
        if (arrayIndexer == null)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            // ...
            LuaAPI.lua_pushstdcallcfunction(L, arrayIndexer);  // 6. 壓入arrayindexer
            // ...
        }
    
        LuaAPI.gen_obj_indexer(L);  // 生成__index元方法
    
        if (type != null)
        {
            LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
            LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables
            translator.Push(L, type);
            LuaAPI.lua_pushvalue(L, -3);
            LuaAPI.lua_rawset(L, -3);  // 注冊表[LuaIndexs][type] = __index函式
            LuaAPI.lua_pop(L, 1);
        }
    
        LuaAPI.lua_rawset(L, meta_idx);
        //end index gen
    
        //begin newindex gen
        LuaAPI.xlua_pushasciistring(L, "__newindex");
        LuaAPI.lua_pushvalue(L, setter_idx);
    
        if (csNewIndexer == null)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            // ...
            LuaAPI.lua_pushstdcallcfunction(L, csNewIndexer);
            // ...
        }
    
        translator.Push(L, type == null ? base_type : type.BaseType());
    
        LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    
        if (arrayNewIndexer == null)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            // ...
            LuaAPI.lua_pushstdcallcfunction(L, arrayNewIndexer);
            // ...
        }
    
        LuaAPI.gen_obj_newindexer(L);  // 生成__newindex元方法
    
        if (type != null)
        {
            LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
            LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua newindexs function tables
            translator.Push(L, type);
            LuaAPI.lua_pushvalue(L, -3);
            LuaAPI.lua_rawset(L, -3);  // 注冊表[LuaNewIndexs][type] = __newindex函式
            LuaAPI.lua_pop(L, 1);
        }
    
        LuaAPI.lua_rawset(L, meta_idx);
        //end new index gen
        LuaAPI.lua_pop(L, 4);
    }
    

    __index元方法是通過呼叫gen_obj_indexer獲得的,在呼叫該方法前會依次壓入6個引數(代碼注釋中有標注),gen_obj_indexer內部又會再壓入一個nil值,用于為baseindex提前占位,共7個引數會作為upvalue關聯到閉包obj_indexer,obj_indexer函式就是__index元方法,它的邏輯是當訪問userdata[key]時,先依次查詢之前通過RegisterFunc填充的methods,getters等表中是否存有對應key的包裹方法,如果有則直接使用,如果沒有則遞回在父類中查找,__newindex元方法是通過呼叫gen_obj_newindexer獲得的,與__index的獲得原理類似,這里就不再列出了,

    // xlua.c
    LUA_API int gen_obj_indexer(lua_State *L) {
        lua_pushnil(L);
        lua_pushcclosure(L, obj_indexer, 7);
        return 0;
    }
    
    //upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex
    //param   --- [1]: obj, [2]: key
    LUA_API int obj_indexer(lua_State *L) {	
        if (!lua_isnil(L, lua_upvalueindex(1))) {  // 如果methods中有key,則使用methods[key]
            lua_pushvalue(L, 2);
            lua_gettable(L, lua_upvalueindex(1));
            if (!lua_isnil(L, -1)) {//has method
                return 1;
            }
            lua_pop(L, 1);
        }
        
        if (!lua_isnil(L, lua_upvalueindex(2))) {  // 如果getters中key,則呼叫getters[key]
            lua_pushvalue(L, 2);
            lua_gettable(L, lua_upvalueindex(2));
            if (!lua_isnil(L, -1)) {//has getter
                lua_pushvalue(L, 1);
                lua_call(L, 1, 1);
                return 1;
            }
            lua_pop(L, 1);
        }
        
        
        if (!lua_isnil(L, lua_upvalueindex(6)) && lua_type(L, 2) == LUA_TNUMBER) {  // 如果arrayindexer中有key且key是數字,則呼叫arrayindexer[key]
            lua_pushvalue(L, lua_upvalueindex(6));
            lua_pushvalue(L, 1);
            lua_pushvalue(L, 2);
            lua_call(L, 2, 1);
            return 1;
        }
        
        if (!lua_isnil(L, lua_upvalueindex(3))) {  // 如果csindexer中有key,則呼叫csindexer[key]
            lua_pushvalue(L, lua_upvalueindex(3));
            lua_pushvalue(L, 1);
            lua_pushvalue(L, 2);
            lua_call(L, 2, 2);
            if (lua_toboolean(L, -2)) {
                return 1;
            }
            lua_pop(L, 2);
        }
        
        if (!lua_isnil(L, lua_upvalueindex(4))) {  // 遞回向上在base中查找
            lua_pushvalue(L, lua_upvalueindex(4));
            while(!lua_isnil(L, -1)) {
                lua_pushvalue(L, -1);
                lua_gettable(L, lua_upvalueindex(5));
                if (!lua_isnil(L, -1)) // found
                {
                    lua_replace(L, lua_upvalueindex(7)); //baseindex = indexfuncs[base]
                    lua_pop(L, 1);
                    break;
                }
                lua_pop(L, 1);
                lua_getfield(L, -1, "BaseType");
                lua_remove(L, -2);
            }
            lua_pushnil(L);
            lua_replace(L, lua_upvalueindex(4));//base = nil
        }
        
        if (!lua_isnil(L, lua_upvalueindex(7))) {  
            lua_settop(L, 2);
            lua_pushvalue(L, lua_upvalueindex(7));  
            lua_insert(L, 1);
            lua_call(L, 2, 1);  // 呼叫父類的__index,indexfuncs[base](obj, key)
            return 1;
        } else {
            return 0;
        }
    }
    
  4. Utils.BeginClassRegister,在對類的靜態值(例如靜態變數,靜態方法等)進行注冊前做一些準備作業,主要是為類生成對應的cls_table表,以及提前創建好static_getter表與static_setter表,后續用來存放靜態欄位對應的get和set包裹方法,注意這里還會為cls_table設定元表meta_table

    // Utils.cs
    public static void BeginClassRegister(Type type, RealStatePtr L, LuaCSFunction creator, int class_field_count,
        int static_getter_count, int static_setter_count)
    {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        LuaAPI.lua_createtable(L, 0, class_field_count);
    
        LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");
        translator.PushAny(L, type);
        LuaAPI.lua_rawset(L, -3);
    
        int cls_table = LuaAPI.lua_gettop(L);
    
        SetCSTable(L, type, cls_table);
    
        LuaAPI.lua_createtable(L, 0, 3);
        int meta_table = LuaAPI.lua_gettop(L);
        if (creator != null)
        {
            LuaAPI.xlua_pushasciistring(L, "__call");
    #if GEN_CODE_MINIMIZE
            translator.PushCSharpWrapper(L, creator);
    #else
            LuaAPI.lua_pushstdcallcfunction(L, creator);
    #endif
            LuaAPI.lua_rawset(L, -3);
        }
    
        if (static_getter_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_createtable(L, 0, static_getter_count);   // 創建好static_getter表
        }
    
        if (static_setter_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_createtable(L, 0, static_setter_count);  // 創建好static_setter表
        }
        LuaAPI.lua_pushvalue(L, meta_table);
        LuaAPI.lua_setmetatable(L, cls_table);  // 設定元表
    }
    

    cls_table表是根據類的命名空間名逐層添加到注冊表中的,主要是通過SetCSTable實作,

    // Utils.cs
    public static void SetCSTable(RealStatePtr L, Type type, int cls_table)
    {
        int oldTop = LuaAPI.lua_gettop(L);
        cls_table = abs_idx(oldTop, cls_table);
        LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    
        List<string> path = getPathOfType(type);
    
        // 對于A.B.C來說
    
        // for回圈處理A.B
        // 1. 注冊表[xlua_csharp_namespace][A] = {} 且出堆疊 注冊表[xlua_csharp_namespace]
        // 2. 注冊表[xlua_csharp_namespace][A][B] = {} 且出堆疊 注冊表[xlua_csharp_namespace][A]
    
        for (int i = 0; i < path.Count - 1; ++i)
        {
            LuaAPI.xlua_pushasciistring(L, path[i]);
            if (0 != LuaAPI.xlua_pgettable(L, -2))
            {
                var err = LuaAPI.lua_tostring(L, -1);
                LuaAPI.lua_settop(L, oldTop);
                throw new Exception("SetCSTable for [" + type + "] error: " + err);
            }
            if (LuaAPI.lua_isnil(L, -1))  // 如果 注冊表[xlua_csharp_namespace] 中沒有key path[i] , 則添加一個 path[i] = {} 鍵值對
            {
                LuaAPI.lua_pop(L, 1);
                LuaAPI.lua_createtable(L, 0, 0);
                LuaAPI.xlua_pushasciistring(L, path[i]);
                LuaAPI.lua_pushvalue(L, -2);
                LuaAPI.lua_rawset(L, -4);
            }
            else if (!LuaAPI.lua_istable(L, -1))
            {
                LuaAPI.lua_settop(L, oldTop);
                throw new Exception("SetCSTable for [" + type + "] error: ancestors is not a table!");
            }
            LuaAPI.lua_remove(L, -2);
        }
    
        // 處理C
        // 注冊表[xlua_csharp_namespace][A][B][C] = cls_table 且出堆疊 [xlua_csharp_namespace][A][B][C]
        LuaAPI.xlua_pushasciistring(L, path[path.Count - 1]);
        LuaAPI.lua_pushvalue(L, cls_table);
        LuaAPI.lua_rawset(L, -3);  
        LuaAPI.lua_pop(L, 1);
    
        // 在 注冊表[xlua_csharp_namespace] 中添加鍵值對 [type對應的lua代理userdata] = cls_table
        LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
        ObjectTranslatorPool.Instance.Find(L).PushAny(L, type);
        LuaAPI.lua_pushvalue(L, cls_table);
        LuaAPI.lua_rawset(L, -3);
        LuaAPI.lua_pop(L, 1);
    }
    

    以A.B.C類為例,將在Lua注冊表中添加以下表結構,而Lua注冊表[xlua_csharp_namespace]實際上對應的就是CS全域表,所以要在xLua中訪問C#類時才可以直接使用CS.A.B.C這樣的形式

    Lua注冊表 = {
        xlua_csharp_namespace = {  -- 就是CS全域表
            A = {
                B = {
                    C = cls_table
                }
            },
        },
    }
    
  5. 多個Utils.RegisterFunc,與BeginObjectRegister到EndObjectRegister之間的RegisterFunc作用相同,將類的每個靜態值對應的包裹方法注冊到對應的Lua表中,靜態變數對應的get和set包裹方法會被分別注冊到static_getter表和static_setter表(只讀的靜態變數除外)

  6. Utils.EndClassRegister,結束對類的靜態值的注冊,與EndObjectRegister類似,但它是為cls_table的元表meta_tabl設定__index元方法和__newindex元方法

    // Utils.cs
    public static void EndClassRegister(Type type, RealStatePtr L, ObjectTranslator translator)
    {
        int top = LuaAPI.lua_gettop(L);
        int cls_idx = abs_idx(top, CLS_IDX);
        int cls_getter_idx = abs_idx(top, CLS_GETTER_IDX);
        int cls_setter_idx = abs_idx(top, CLS_SETTER_IDX);
        int cls_meta_idx = abs_idx(top, CLS_META_IDX);
    
        //begin cls index
        LuaAPI.xlua_pushasciistring(L, "__index");
        LuaAPI.lua_pushvalue(L, cls_getter_idx);
        LuaAPI.lua_pushvalue(L, cls_idx);
        translator.Push(L, type.BaseType());
        LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);  
        LuaAPI.gen_cls_indexer(L);
    
        LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables  
        translator.Push(L, type);
        LuaAPI.lua_pushvalue(L, -3);
        LuaAPI.lua_rawset(L, -3);  // 注冊表[LuaClassIndexs][type] = __index函式
        LuaAPI.lua_pop(L, 1);
    
        LuaAPI.lua_rawset(L, cls_meta_idx);
        //end cls index
    
        //begin cls newindex
        LuaAPI.xlua_pushasciistring(L, "__newindex");
        LuaAPI.lua_pushvalue(L, cls_setter_idx);
        translator.Push(L, type.BaseType());
        LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
        LuaAPI.gen_cls_newindexer(L);
    
        LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua newindexs function tables
        translator.Push(L, type);
        LuaAPI.lua_pushvalue(L, -3);
        LuaAPI.lua_rawset(L, -3);  // 注冊表[LuaClassNewIndexs][type] = __newindex函式
        LuaAPI.lua_pop(L, 1);
    
        LuaAPI.lua_rawset(L, cls_meta_idx);
        //end cls newindex
    
        LuaAPI.lua_pop(L, 4);
    }
    

上述6個部分的代碼量比較大,邏輯也比較復雜,到這里有必要做一個總結,

生成代碼會為類的非靜態值都生成對應的包裹方法,并將包裹方法以 key = func 的形式注冊到不同的表中,userdata元表的__index和__newindex負責從這不同的表中找到對應key的包裹方法,最終通過呼叫包裹方法實作對C#物件的控制

-- lua測驗代碼
local obj = CS.TestXLua()
obj.Name = "test"  -- 賦值操作將觸發obj元表的__newindex,__newindex在setter表中找到Name對應的set包裹方法_s_set_Name,然后通過呼叫_s_set_Name方法設定了TestXLua物件的Name屬性為"test"

生成代碼還會為每個類以命名空間為層次結構生成cls_table表,與類的非靜態值相同,生成代碼也會為類的靜態值都生成對應的包裹方法并注冊到不同的表中(注意這里有些區別,類的靜態方法會被直接注冊到cls_table表中),而cls_table元表的__index和__newindex負責從這不同的表中找到對應key的包裹方法,最終通過呼叫包裹方法實作對C#類的控制

-- lua測驗代碼
CS.TestXLua.Test2()  -- CS.TestXLua獲取到TestXLua類對應的cls_table,由于Test2是靜態方法,在cls_table中可以直接拿到其對應的包裹方法_m_Test2_xlua_st_,然后通過呼叫_m_Test2_xlua_st_而間接呼叫了TestXLua類的Test2方法

使用反射填充元表

當沒有生成代碼時,會使用反射進行注冊,與生成代碼進行注冊的邏輯基本相同,通過反射獲取到類的各個靜態值和非靜態值,然后分別注冊到不同的表中,以及填充__index和__newindex元方法

// Utils.cs
public static void ReflectionWrap(RealStatePtr L, Type type, bool privateAccessible)
{
    LuaAPI.lua_checkstack(L, 20);

    int top_enter = LuaAPI.lua_gettop(L);
    ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
    //create obj meta table
    LuaAPI.luaL_getmetatable(L, type.FullName);
    if (LuaAPI.lua_isnil(L, -1))
    {
        LuaAPI.lua_pop(L, 1);
        LuaAPI.luaL_newmetatable(L, type.FullName);
    }
    // 為元表添加xlua_tag標志
    LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag());
    LuaAPI.lua_pushnumber(L, 1);
    LuaAPI.lua_rawset(L, -3);  // 元表[xlua_tag] = 1
    int obj_meta = LuaAPI.lua_gettop(L);  

    LuaAPI.lua_newtable(L);
    int cls_meta = LuaAPI.lua_gettop(L);

    LuaAPI.lua_newtable(L);
    int obj_field = LuaAPI.lua_gettop(L);
    LuaAPI.lua_newtable(L);
    int obj_getter = LuaAPI.lua_gettop(L);
    LuaAPI.lua_newtable(L);
    int obj_setter = LuaAPI.lua_gettop(L);
    LuaAPI.lua_newtable(L);
    int cls_field = LuaAPI.lua_gettop(L);
    //set cls_field to namespace
    SetCSTable(L, type, cls_field);
    //finish set cls_field to namespace
    LuaAPI.lua_newtable(L);
    int cls_getter = LuaAPI.lua_gettop(L);
    LuaAPI.lua_newtable(L);
    int cls_setter = LuaAPI.lua_gettop(L);

    LuaCSFunction item_getter;
    LuaCSFunction item_setter;
    makeReflectionWrap(L, type, cls_field, cls_getter, cls_setter, obj_field, obj_getter, obj_setter, obj_meta,
        out item_getter, out item_setter, privateAccessible ? (BindingFlags.Public | BindingFlags.NonPublic) : BindingFlags.Public);

    // init obj metatable
    LuaAPI.xlua_pushasciistring(L, "__gc");
    LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
    LuaAPI.lua_rawset(L, obj_meta);

    LuaAPI.xlua_pushasciistring(L, "__tostring");
    LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);
    LuaAPI.lua_rawset(L, obj_meta);

    LuaAPI.xlua_pushasciistring(L, "__index");
    LuaAPI.lua_pushvalue(L, obj_field);  // 1.upvalue methods = obj_field
    LuaAPI.lua_pushvalue(L, obj_getter);  // 2.upvalue getters = obj_getter
    translator.PushFixCSFunction(L, item_getter);  // 3.upvalue csindexer = item_getter
    translator.PushAny(L, type.BaseType());  // 壓入BaseType,4.upvalue base
    LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);  // 5.upvalue indexfuncs = 注冊表[LuaIndexs]
    LuaAPI.lua_pushnil(L);  // 6.upvalue arrayindexer = nil
    LuaAPI.gen_obj_indexer(L);  // 生成__index函式
    //store in lua indexs function tables
    LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);  
    translator.Push(L, type);  // 壓入type
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_rawset(L, -3);  // 注冊表[LuaIndexs][type] = __index函式
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_rawset(L, obj_meta); // set __index  即 obj_meta["__index"] = 生成的__index函式

    LuaAPI.xlua_pushasciistring(L, "__newindex");
    LuaAPI.lua_pushvalue(L, obj_setter);
    translator.PushFixCSFunction(L, item_setter);
    translator.Push(L, type.BaseType());
    LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    LuaAPI.lua_pushnil(L);
    LuaAPI.gen_obj_newindexer(L);
    //store in lua newindexs function tables
    LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_rawset(L, -3);  // 注冊表[LuaNewIndexs][type] = __newindex函式
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_rawset(L, obj_meta); // set __newindex
                                    //finish init obj metatable

    LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");
    translator.PushAny(L, type);
    LuaAPI.lua_rawset(L, cls_field);  // cls_field["UnderlyingSystemType"] = type  , 記錄類的基礎型別

    if (type != null && type.IsEnum())
    {
        LuaAPI.xlua_pushasciistring(L, "__CastFrom");
        translator.PushFixCSFunction(L, genEnumCastFrom(type));
        LuaAPI.lua_rawset(L, cls_field);
    }

    //init class meta
    LuaAPI.xlua_pushasciistring(L, "__index");
    LuaAPI.lua_pushvalue(L, cls_getter);
    LuaAPI.lua_pushvalue(L, cls_field);
    translator.Push(L, type.BaseType());
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    LuaAPI.gen_cls_indexer(L);
    //store in lua indexs function tables
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_rawset(L, -3);  // 注冊表[LuaClassIndexs][type] = __index函式
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_rawset(L, cls_meta); // set __index 

    LuaAPI.xlua_pushasciistring(L, "__newindex");
    LuaAPI.lua_pushvalue(L, cls_setter);
    translator.Push(L, type.BaseType());
    LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    LuaAPI.gen_cls_newindexer(L);
    //store in lua newindexs function tables
    LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_rawset(L, -3);  // // 注冊表[LuaClassNewIndexs][type] = __newindex函式
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_rawset(L, cls_meta); // set __newindex
    // ...
}

呼叫C#方法時引數的傳遞

先來解決前面遺留的一個問題,對于類的靜態值或是非靜態值為什么都需要生成對應的包裹方法?其實包裹方法就是用來處理引數傳遞問題的,

為了正確的和Lua通訊,C函式已經定義好了協議,這個協議定義了引數以及回傳值傳遞方法:C函式通過Lua中的堆疊來接受引數,引數以正序入堆疊(第一個引數首先入堆疊),因此,當函式開始的時候,lua_gettop(L)可以回傳函式收到的引數個數,第一個引數(如果有的話)在索引1的地方,而最后一個引數在索引lua_gettop(L)處,當需要向Lua回傳值的時候,C函式只需要把它們以正序壓到堆疊上(第一個回傳值最先壓入),然后回傳這些回傳值的個數,在這些回傳值之下的,堆疊上的東西都會被Lua丟掉,和Lua函式一樣,從Lua中呼叫C函式可以有很多回傳值,

也就是說,Lua這邊呼叫C函式時的引數會被自動的壓堆疊,這套機制Lua內部已經實作好了,文章開頭也提到,C#可以借助C/C++來與Lua進行資料通信,所以C#需要通過C API獲取到Lua傳遞過來的引數,而這個邏輯就被封裝在了包裹方法中,以TestXLua的Test1方法為例,它需要一個int引數,所以它的包裹方法需要通過C API獲取到一個int引數,然后再使用這個引數去呼叫真正的方法

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_Test1(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
        {
            int _a = LuaAPI.xlua_tointeger(L, 2);  // 獲取到int引數
            gen_to_be_invoked.Test1( _a );  // 呼叫真正的Test1方法
            return 0;
        }
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
}

這也解釋了為什么需要為類的屬性生成對應的get和set方法,因為只有將Lua的訪問或賦值操作轉換成函式呼叫形式時,引數才能利用函式呼叫機制被自動的壓堆疊,從而傳遞給C#

-- lua測驗代碼
obj.Name = "test"  -- 賦值操作
setter["Name"]("test")  -- 函式呼叫形式

這里再提一下函式多載的問題,因為C#是支持多載的,所以會存在多個同名函式,但引數不同的情況,對于這種情況,只能通過同名函式被呼叫時傳遞的引數情況來判斷到底應該呼叫哪個函式

[LuaCallCSharp]
public class TestXLua
{
    // 函式多載Test1
    public void Test1(int a){
    }
    // 函式多載Test1
    public void Test1(bool b){
    }
}

// 為Test1生成的包裹方法
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_Test1(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
        int gen_param_count = LuaAPI.lua_gettop(L);
        if(gen_param_count == 2&& LuaTypes.LUA_TNUMBER == LuaAPI.lua_type(L, 2))  // 根據引數數量與型別判斷呼叫哪個方法
        {
            int _a = LuaAPI.xlua_tointeger(L, 2);
            gen_to_be_invoked.Test1( _a );
            return 0;
        }
        if(gen_param_count == 2&& LuaTypes.LUA_TBOOLEAN == LuaAPI.lua_type(L, 2))  // 根據引數數量與型別判斷呼叫哪個方法
        { 
            bool _b = LuaAPI.lua_toboolean(L, 2);
            gen_to_be_invoked.Test1( _b );
            return 0;
        }
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return LuaAPI.luaL_error(L, "invalid arguments to TestXLua.Test1!");
}

GC

C#和Lua都是有自動垃圾回識訓制的,并且相互是無感知的,如果傳遞到Lua的C#物件被C#自動回收掉了,而Lua這邊仍毫不知情繼續使用,則必然會導致無法預知的錯誤,所以基本原則是傳遞到Lua的C#物件,C#不能自動回收,只能Lua在確定不再使用后通知C#進行回收

為了保證C#不會自動回收物件,所有傳遞給Lua的物件都會被objects保持參考,真實傳遞給Lua的物件索引就是物件在objects中的索引

Lua這邊為物件索引建立的userdata會被保存在快取表中,而快取表的參考模式被設定為弱參考

// ObjectTranslator.cs
LuaAPI.lua_newtable(L);  // 創建快取表
LuaAPI.lua_newtable(L);  // 創建元表
LuaAPI.xlua_pushasciistring(L, "__mode");
LuaAPI.xlua_pushasciistring(L, "v");
LuaAPI.lua_rawset(L, -3);  // 元表[__mode] = v,表示這張表的所有值皆為弱參考
LuaAPI.lua_setmetatable(L, -2);  // 為快取表設定元表
cacheRef = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);

當Lua這邊不再參考這個userdata時,userdata會被從快取表中移除,Lua GC時會回收這個userdata,回收之前又會呼叫userdata元表的__gc方法,以此來通知C#,"我Lua這邊不再使用這個物件了,你該回收可以回收了",在BeginObjectRegister方法內部,會為userdata的元表添加__gc方法

// Utils.cs BeginObjectRegister方法
if ((type == null || !translator.HasCustomOp(type)) && type != typeof(decimal))
{
    LuaAPI.xlua_pushasciistring(L, "__gc");
    LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
    LuaAPI.lua_rawset(L, -3);  // 為元表設定__gc方法
}

translator.metaFunctions.GcMeta實際上就是StaticLuaCallbacks的LuaGC方法

// StaticLuaCallbacks.cs
[MonoPInvokeCallback(typeof(LuaCSFunction))]
public static int LuaGC(RealStatePtr L)
{
    try
    {
        int udata = https://www.cnblogs.com/iwiniwin/archive/2021/09/18/LuaAPI.xlua_tocsobj_safe(L, 1);
        if (udata != -1)
        {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
            if ( translator != null )
            {
                translator.collectObject(udata);
            }
        }
        return 0;
    }
    catch (Exception e)
    {
        return LuaAPI.luaL_error(L,"c# exception in LuaGC:" + e);
    }
}

LuaGC方法又會呼叫collectObject方法,在collectObject方法內部會將物件從objects移除,從而使物件不再被固定參考,能夠被C# GC正常回收

// ObjectTranslator.cs
internal void collectObject(int obj_index_to_collect)
{
    object o;
    
    if (objects.TryGetValue(obj_index_to_collect, out o))
    {
        objects.Remove(obj_index_to_collect);
        
        if (o != null)
        {
            int obj_index;
            //lua gc是先把weak table移除后再呼叫__gc,這期間同一個物件可能再次push到lua,關聯到新的index
            bool is_enum = o.GetType().IsEnum();
            if ((is_enum ? enumMap.TryGetValue(o, out obj_index) : reverseMap.TryGetValue(o, out obj_index))
                && obj_index == obj_index_to_collect)
            {
                if (is_enum)
                {
                    enumMap.Remove(o);
                }
                else
                {
                    reverseMap.Remove(o);
                }
            }
        }
    }
}

參考

  • 添加了中文注釋的xLua原始碼
  • 注冊C#類到Lua中后Lua注冊表的模擬結構
  • 看懂Xlua實作原理
作者:iwiniwin 出處:http://www.cnblogs.com/iwiniwin/ 本文為博主原創文章,轉載請附上原文出處鏈接和本宣告,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/301146.html

標籤:.NET技术

上一篇:Consul+Ocelot+Polly在.NetCore中使用(.NET5)-Consul服務注冊,服務發現

下一篇:在樹莓派用C#+Winform實作傳感器監測

標籤雲
其他(123570) Java(13369) Python(12731) C(7545) 區塊鏈(7372) JavaScript(7059) 基礎類(6313) AI(6244) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4120) MySQL(4012) Linux(3394) C語言(3288) C++語言(3117) Java相關(2746) 疑難問題(2699) 單片機工控(2479) Web開發(1951) 網絡通信(1793) 數據庫相關(1767) VB基礎類(1755) PHP(1727) 開發(1646) 系統維護與使用區(1617) .NETCore(1586) 基礎和管理(1579) JavaEE(1566) C++(1527) 專題技術討論區(1515) Windows客戶端使用(1484) HtmlCss(1466) ASP.NET(1428) Unity3D(1354) VCL組件開發及應用(1353) HTML(CSS)(1220) 其他技術討論專區(1200) WindowsServer(1192) .NET技术(1165) 交換及路由技術(1149) 語言基礎算法系統設計(1133) WindowsSDKAPI(1124) 界面(1088) JavaSE(1075) Qt(1074) VBA(1048) 新手樂園(1016) 其他開發語言(947) Go(907) HTML5(901) 新技術前沿(898) 硬件設計(872) 區塊鏈技術(860) 網絡編程(857) 非技術版(846) 一般軟件使用(839) 網絡協議與配置(835) Eclipse(790) Spark(750) 下載資源懸賞專區(743)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布