深入xLua實現原理之Lua如何調用C#

iwiniwin 2021-09-18 10:20:22 阅读数:670

深入 xlua 原理 lua c#

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 = 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);
}
}
}
}
}

參考

版权声明:本文为[iwiniwin]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210918102021863k.html