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


本文主要是探討xLua下C#調用Lua的實現原理,有關Lua如何調用C#的介紹可以查看深入xLua實現原理之Lua如何調用C#

C#與Lua數據通信機制

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

除了普通的值類型之外,Lua中比較特殊但又很常用的大概就是table和funciton這兩種類型了,下面逐一來分析

傳遞Lua table到C#

以TestXLua類為例來看Lua table是如何被傳遞的,TestXLua有一個LuaTable類型的靜態變量,LuaTable是C#這邊定義的一個類,封裝了一些對Lua table的操作

// 注意,這里添加的LuaCallCSharp特性只是為了使xLua為其生成代碼,不添加並不影響功能
[LuaCallCSharp]
public class TestXLua
{
    public static LuaTable tab;
}

在點擊Generate Code之后,部分生成代碼如下所示。為tab變量生成了對應的set和get包裹方法

// TestXLuaWrap.cs
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _g_get_tab(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        translator.Push(L, TestXLua.tab);
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 1;
}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _s_set_tab(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        TestXLua.tab = (XLua.LuaTable)translator.GetObject(L, 1, typeof(XLua.LuaTable));
    
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 0;
}

為tab靜態變量賦值一個Lua table,table中包含一個 num = 1 鍵值對

-- Lua測試代碼
local t = {
    num = 1
}
CS.TestXLua.tab = t

上述代碼在賦值時,最終會調用到_s_set_tab包裹方法(具體原理可以查看這里),Lua這邊調用_s_set_tab前,會先將參數table t壓入到棧中,因此_s_set_tab內部需要通過translator.GetObject拿到這個table,並將其賦值給tab靜態變量

// ObjectTranslator.cs
public object GetObject(RealStatePtr L, int index, Type type)
{
    int udata = LuaAPI.xlua_tocsobj_safe(L, index);

    if (udata != -1)
    {
        // 對C#對象的處理
        object obj = objects.Get(udata);
        RawObject rawObject = obj as RawObject;
        return rawObject == null ? obj : rawObject.Target;
    }
    else
    {
        if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA)
        {
            GetCSObject get;
            int type_id = LuaAPI.xlua_gettypeid(L, index);
            if (type_id != -1 && type_id == decimal_type_id)
            {
                decimal d;
                Get(L, index, out d);
                return d;
            }
            Type type_of_struct;
            if (type_id != -1 && typeMap.TryGetValue(type_id, out type_of_struct) && type.IsAssignableFrom(type_of_struct) && custom_get_funcs.TryGetValue(type, out get))
            {
                return get(L, index);
            }
        }
        return (objectCasters.GetCaster(type)(L, index, null));
    }
}

GetObject方法負責從棧中獲取指定類型的對象,對於LuaTable類型是通過objectCasters.GetCaster獲取轉換器后,通過轉換器函數轉換得到

// ObjectTranslator.cs
public ObjectCast GetCaster(Type type)
{
    if (type.IsByRef) type = type.GetElementType();  // 如果是按引用傳遞的,則使用引用的對象的type

    Type underlyingType = Nullable.GetUnderlyingType(type);
    if (underlyingType != null)
    {
        return genNullableCaster(GetCaster(underlyingType)); 
    }
    ObjectCast oc;
    if (!castersMap.TryGetValue(type, out oc))
    {
        oc = genCaster(type);
        castersMap.Add(type, oc);
    }
    return oc;
}

xLua已經默認在castersMap中為一些類型定義好了轉換函數,其中就包括LuaTable類型

// ObjectCasters.cs
public ObjectCasters(ObjectTranslator translator)
{
    this.translator = translator;
    castersMap[typeof(char)] = charCaster;
    castersMap[typeof(sbyte)] = sbyteCaster;
    castersMap[typeof(byte)] = byteCaster;
    castersMap[typeof(short)] = shortCaster;
    castersMap[typeof(ushort)] = ushortCaster;
    castersMap[typeof(int)] = intCaster;
    castersMap[typeof(uint)] = uintCaster;
    castersMap[typeof(long)] = longCaster;
    castersMap[typeof(ulong)] = ulongCaster;
    castersMap[typeof(double)] = getDouble;
    castersMap[typeof(float)] = floatCaster;
    castersMap[typeof(decimal)] = decimalCaster;
    castersMap[typeof(bool)] = getBoolean;
    castersMap[typeof(string)] =  getString;
    castersMap[typeof(object)] = getObject;
    castersMap[typeof(byte[])] = getBytes;
    castersMap[typeof(IntPtr)] = getIntptr;
    //special type
    castersMap[typeof(LuaTable)] = getLuaTable;
    castersMap[typeof(LuaFunction)] = getLuaFunction;
}

LuaTable對應的轉換函數是getLuaTable

// ObjectCasters.cs
private object getLuaTable(RealStatePtr L, int idx, object target)
{
    if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA)
    {
        object obj = translator.SafeGetCSObj(L, idx);
        return (obj != null && obj is LuaTable) ? obj : null;
    }
    if (!LuaAPI.lua_istable(L, idx))
    {
        return null;
    }
    // 處理普通table類型
    LuaAPI.lua_pushvalue(L, idx);
    return new LuaTable(LuaAPI.luaL_ref(L), translator.luaEnv);
}

getLuaTable的主要邏輯是將idx處的table通過luaL_ref添加到Lua注冊表中並得到指向該table的索引,然后創建LuaTable對象保存該索引。也就是說Lua table在C#這邊對應的是LuaTable對象,它們之間通過一個索引關聯起來,這個索引表示Lua table在Lua注冊表中的引用,利用這個索引可以獲取到Lua table。拿到Lua table后,就可以繼續訪問Lua table的內容了。

// CS測試代碼
int num = TestXLua.tab.Get<int>("num");

對Lua table的訪問操作都被封裝在LuaTable的Get方法中

// LuaTable.cs
public TValue Get<TValue>(string key)
{
    TValue ret;
    Get(key, out ret);
    return ret;
}

// no boxing version get
public void Get<TKey, TValue>(TKey key, out TValue value)
{
#if THREAD_SAFE || HOTFIX_ENABLE
    lock (luaEnv.luaEnvLock)
    {
#endif
        var L = luaEnv.L;
        var translator = luaEnv.translator;
        int oldTop = LuaAPI.lua_gettop(L);
        LuaAPI.lua_getref(L, luaReference);  // 通過luaReference獲取到對應的table
        translator.PushByType(L, key);

        if (0 != LuaAPI.xlua_pgettable(L, -2))  // 查詢 表[key]
        {
            string err = LuaAPI.lua_tostring(L, -1);
            LuaAPI.lua_settop(L, oldTop);
            throw new Exception("get field [" + key + "] error:" + err);
        }

        LuaTypes lua_type = LuaAPI.lua_type(L, -1);
        Type type_of_value = typeof(TValue);
        if (lua_type == LuaTypes.LUA_TNIL && type_of_value.IsValueType())
        {
            throw new InvalidCastException("can not assign nil to " + type_of_value.GetFriendlyName());
        }

        try
        {
            translator.Get(L, -1, out value);  // 獲取棧頂的元素,即 表[key]
        }
        catch (Exception e)
        {
            throw e;
        }
        finally
        {
            LuaAPI.lua_settop(L, oldTop);
        }
#if THREAD_SAFE || HOTFIX_ENABLE
    }
#endif
}

Get方法的主要邏輯是,先通過保存的索引luaReference獲取到Lua table,然后通過xlua_pgettable將 表[key] 的值壓棧,最后通過translator.Get獲取到棧頂值對應的對象

// ObjectTranslator.cs
public void Get<T>(RealStatePtr L, int index, out T v)
{
    Func<RealStatePtr, int, T> get_func;
    if (tryGetGetFuncByType(typeof(T), out get_func))
    {
        v = get_func(L, index);  // 將給定索引處的值轉換為{T}類型
    }
    else
    {
        v = (T)GetObject(L, index, typeof(T));
    }
}

同樣的,xLua也在tryGetGetFuncByType中為一些基本類型預定義好了對應的對象獲取方法,采取泛型方式,這樣可以避免拆箱和裝箱。在本例中獲取的值 num = 1 是一個int類型,通過轉換器函數xlua_tointeger即可獲得。xlua_tointeger就是對Lua原生API lua_tointeger的一個簡單封裝

bool tryGetGetFuncByType<T>(Type type, out T func) where T : class
{
    if (get_func_with_type == null)
    {
        get_func_with_type = new Dictionary<Type, Delegate>()
        {
            {typeof(int), new Func<RealStatePtr, int, int>(LuaAPI.xlua_tointeger) },
            {typeof(double), new Func<RealStatePtr, int, double>(LuaAPI.lua_tonumber) },
            {typeof(string), new Func<RealStatePtr, int, string>(LuaAPI.lua_tostring) },
            {typeof(byte[]), new Func<RealStatePtr, int, byte[]>(LuaAPI.lua_tobytes) },
            {typeof(bool), new Func<RealStatePtr, int, bool>(LuaAPI.lua_toboolean) },
            {typeof(long), new Func<RealStatePtr, int, long>(LuaAPI.lua_toint64) },
            {typeof(ulong), new Func<RealStatePtr, int, ulong>(LuaAPI.lua_touint64) },
            {typeof(IntPtr), new Func<RealStatePtr, int, IntPtr>(LuaAPI.lua_touserdata) },
            {typeof(decimal), new Func<RealStatePtr, int, decimal>((L, idx) => {
                decimal ret;
                Get(L, idx, out ret);
                return ret;
            }) },
            {typeof(byte), new Func<RealStatePtr, int, byte>((L, idx) => (byte)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(sbyte), new Func<RealStatePtr, int, sbyte>((L, idx) => (sbyte)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(char), new Func<RealStatePtr, int, char>((L, idx) => (char)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(short), new Func<RealStatePtr, int, short>((L, idx) => (short)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(ushort), new Func<RealStatePtr, int, ushort>((L, idx) => (ushort)LuaAPI.xlua_tointeger(L, idx) ) },
            {typeof(uint), new Func<RealStatePtr, int, uint>(LuaAPI.xlua_touint) },
            {typeof(float), new Func<RealStatePtr, int, float>((L, idx) => (float)LuaAPI.lua_tonumber(L, idx) ) },
        };
    }

傳遞Lua function到C#

Lua的function傳遞到C#后,對應的是C#的委托,同樣以TestXLua類為例來分析具體過程

// 注意,這里添加的LuaCallCSharp特性只是為了使xLua為其生成代碼,不添加並不影響功能
[LuaCallCSharp]
public class TestXLua
{
    [CSharpCallLua]
    public delegate int Func(string s, bool b, float f);

    public static Func func;
}

點擊Generate Code后,生成的部分TestXLuaWrap代碼如下所示。為func變量生成了對應的set和get包裹方法

// TestXLuaWrap.cs
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _g_get_func(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        translator.Push(L, TestXLua.func);
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 1;
}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _s_set_func(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        TestXLua.func = translator.GetDelegate<TestXLua.Func>(L, 1);
    
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 0;
}

為func靜態變量賦值一個Lua function

-- Lua測試代碼
CS.TestXLua.func = function(s, b, i)
    
end

上述代碼在賦值時,會最終調用_s_set_func包裹方法(具體原理可以查看這里),Lua在調用_s_set_func前,會將參數function壓入到棧中,因此_s_set_func內部需要通過translator.GetDelegate拿到這個function,並將其賦值給func靜態變量

// ObjectTranslator.cs
public T GetDelegate<T>(RealStatePtr L, int index) where T :class
{
    
    if (LuaAPI.lua_isfunction(L, index))
    {
        return CreateDelegateBridge(L, typeof(T), index) as T;
    }
    else if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA)
    {
        return (T)SafeGetCSObj(L, index);
    }
    else
    {
        return null;
    }
}

對於Lua function類型會通過CreateDelegateBridge創建一個對應的委托並返回。CreateDelegateBridge內部會創建一個DelegateBridge對象來對應Lua function,原理和LuaTable類似,也是通過一個索引保持聯系,利用這個索引可以獲取到Lua function

// ObjectTranslator.cs
Dictionary<int, WeakReference> delegate_bridges = new Dictionary<int, WeakReference>();  // 弱引用創建的DelegateBridge
public object CreateDelegateBridge(RealStatePtr L, Type delegateType, int idx)
{
    LuaAPI.lua_pushvalue(L, idx);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    // 對緩存的處理
    if (!LuaAPI.lua_isnil(L, -1))
    {
        int referenced = LuaAPI.xlua_tointeger(L, -1);
        LuaAPI.lua_pop(L, 1);

        if (delegate_bridges[referenced].IsAlive)
        {
            if (delegateType == null)
            {
                return delegate_bridges[referenced].Target;
            }
            DelegateBridgeBase exist_bridge = delegate_bridges[referenced].Target as DelegateBridgeBase;
            Delegate exist_delegate;
            if (exist_bridge.TryGetDelegate(delegateType, out exist_delegate))
            {
                return exist_delegate;
            }
            else
            {
                exist_delegate = getDelegate(exist_bridge, delegateType);
                exist_bridge.AddDelegate(delegateType, exist_delegate);
                return exist_delegate;
            }
        }
    }
    else
    {
        LuaAPI.lua_pop(L, 1);
    }

    LuaAPI.lua_pushvalue(L, idx);
    int reference = LuaAPI.luaL_ref(L);  // 將idx處的元素添加到Lua注冊表中
    LuaAPI.lua_pushvalue(L, idx);
    LuaAPI.lua_pushnumber(L, reference);
    LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);  // 注冊表[idx值] = reference
    DelegateBridgeBase bridge;
    try
    {
#if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0
        if (!DelegateBridge.Gen_Flag)
        {
            bridge = Activator.CreateInstance(delegate_birdge_type, new object[] { reference, luaEnv }) as DelegateBridgeBase;  // 使用反射創建DelegateBridge對象
        }
        else
#endif
        {
            bridge = new DelegateBridge(reference, luaEnv);
        }
    }
    catch(Exception e)
    {
        LuaAPI.lua_pushvalue(L, idx);
        LuaAPI.lua_pushnil(L);
        LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);
        LuaAPI.lua_pushnil(L);
        LuaAPI.xlua_rawseti(L, LuaIndexes.LUA_REGISTRYINDEX, reference);
        throw e;
    }
    if (delegateType == null)
    {
        delegate_bridges[reference] = new WeakReference(bridge);
        return bridge;
    }
    try {
        var ret = getDelegate(bridge, delegateType);  // 通過bridge獲取到指定類型的委托
        bridge.AddDelegate(delegateType, ret);
        delegate_bridges[reference] = new WeakReference(bridge);
        return ret;
    }
    catch(Exception e)
    {
        bridge.Dispose();
        throw e;
    }
}

在取得DelegateBridge對象后,還需要通過getDelegate方法,獲取delegateType類型的委托,即C#這邊指定要接收Lua function時聲明的委托類型。在本例中是typeof(TestXLua.Func)

Delegate getDelegate(DelegateBridgeBase bridge, Type delegateType)
{
    // ...
    Func<DelegateBridgeBase, Delegate> delegateCreator;
    if (!delegateCreatorCache.TryGetValue(delegateType, out delegateCreator))
    {
        // get by parameters
        MethodInfo delegateMethod = delegateType.GetMethod("Invoke");
        // 生成代碼為配置了 CSharpCallLua的委托 生成以__Gen_Delegate_Imp開頭的方法 並添加到 DelegateBridge 類中
        var methods = bridge.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => !m.IsGenericMethodDefinition && (m.Name.StartsWith("__Gen_Delegate_Imp") || m.Name == "Action")).ToArray();
        // 查找bridge中與delegateMethod匹配的方法,這個方法必須是以__Gen_Delegate_Imp或Action開頭
        for (int i = 0; i < methods.Length; i++)  
        {
            if (!methods[i].IsConstructor && Utils.IsParamsMatch(delegateMethod, methods[i]))
            {
                var foundMethod = methods[i];
                delegateCreator = (o) =>
#if !UNITY_WSA || UNITY_EDITOR
                    Delegate.CreateDelegate(delegateType, o, foundMethod);  // 創建表示foundMethod的delegateType類型的委托
#else
                    foundMethod.CreateDelegate(delegateType, o); 
#endif
                break;
            }
        }

        if (delegateCreator == null)
        {
            delegateCreator = getCreatorUsingGeneric(bridge, delegateType, delegateMethod);
        }
        delegateCreatorCache.Add(delegateType, delegateCreator);
    }

    ret = delegateCreator(bridge);  // 創建委托
    if (ret != null)
    {
        return ret;
    }

    throw new InvalidCastException("This type must add to CSharpCallLua: " + delegateType.GetFriendlyName());
}

如何利用bridge獲取到指定類型delegateType的委托呢?主要邏輯是,先獲得delegateType委托的Invoke方法,然后通過反射遍歷bridge類型的所有方法,找到與Invoke參數匹配的目標方法。再使用bridge實例與目標方法創建一個delegateType類型的委托。換言之,這個delegateType類型的委托綁定的是bridge的與之參數匹配的成員方法,而且這個方法名稱要以"__Gen_Delegate_Imp"開頭

用於接收Lua function的委托必須添加CSharpCallLua特性也正是因為要為其生成以"__Gen_Delegate_Imp"開頭的方法,如果不添加則會拋出異常

 c# exception:System.InvalidCastException: This type must add to CSharpCallLua: TestXLua+Func

添加CSharpCallLua特性后,點擊Generate Code,會為該委托生成如下代碼。雖然代碼生成在DelegatesGensBridge.cs文件中,但它通過partial聲明為DelegateBridge類的一部分。生成的函數名均以"__Gen_Delegate_Imp"開頭,且參數類型和個數與該委托一致

// DelegatesGensBridge.cs
public partial class DelegateBridge : DelegateBridgeBase
{
    // ...
    public int __Gen_Delegate_Imp1(string p0, bool p1, float p2)
    {
#if THREAD_SAFE || HOTFIX_ENABLE
        lock (luaEnv.luaEnvLock)
        {
#endif
            RealStatePtr L = luaEnv.rawL;
            int errFunc = LuaAPI.pcall_prepare(L, errorFuncRef, luaReference);
            
            LuaAPI.lua_pushstring(L, p0);  // 壓棧參數
            LuaAPI.lua_pushboolean(L, p1);  // 壓棧參數
            LuaAPI.lua_pushnumber(L, p2);  // 壓棧參數
            
            PCall(L, 3, 1, errFunc);  // Lua function調用
            
            
            int __gen_ret = LuaAPI.xlua_tointeger(L, errFunc + 1);
            LuaAPI.lua_settop(L, errFunc - 1);
            return  __gen_ret;
#if THREAD_SAFE || HOTFIX_ENABLE
        }
#endif
    }
}

TestXLua.Func類型委托綁定的就是這個生成函數__Gen_Delegate_Imp1。之所以要使用生成函數,是因為需要生成函數來完成參數的壓棧與Lua function調用

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

文章開頭也已提到,C#可以借助C/C++來與Lua進行數據通信,所以C#在函數調用前,需要通過C API來壓棧函數調用所需的參數,而這個邏輯就被封裝在了以"__Gen_Delegate_Imp"開頭的生成方法中。生成方法將參數壓棧后,再通過PCall調用Lua function,PCall內部調用的就是Lua原生API lua_pcall

總結一下整個流程

-- Lua測試代碼
CS.TestXLua.func = function(s, b, i)
    
end

當為TestXLua.func賦值Lua function時,會觸發func變量的set包裹方法_s_set_func,_s_set_func內部會獲取一個委托設置給func變量。這個委托綁定的是DelegateBridge對象的以"__Gen_Delegate_Imp"開頭的生成方法,DelegateBridge對象同時保存着Lua function的索引

// CS測試代碼
TestXLua.func("test", false, 3);

當調用TestXLua.func時,相當於調用以"__Gen_Delegate_Imp"開頭的生成方法,這個生成方法負責參數壓棧,並通過保存的索引獲取到Lua function,然后使用lua_pcall完成Lua function的調用

GC

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

為了保證Lua不會自動回收對象,所有傳遞給C#的對象都會被Lua注冊表引用。比如前面創建LuaTable或DelegateBridge時 都有調用LuaAPI.luaL_ref將對象添加到注冊表中

C#這邊為對應的Lua對象定義了LuaBase基類,LuaTable或DelegateBridge均派生於LuaBase,這個類實現了IDisposable接口,並且在析構函數中會調用Dispose

// LuaBase.cs
public virtual void Dispose(bool disposeManagedResources)
{
    if (!disposed)
    {
        if (luaReference != 0)
        {
#if THREAD_SAFE || HOTFIX_ENABLE
            lock (luaEnv.luaEnvLock)
            {
#endif
                bool is_delegate = this is DelegateBridgeBase;
                if (disposeManagedResources)
                {
                    luaEnv.translator.ReleaseLuaBase(luaEnv.L, luaReference, is_delegate);  // 釋放Lua對象
                }
                else //will dispse by LuaEnv.GC
                {
                    luaEnv.equeueGCAction(new LuaEnv.GCAction { Reference = luaReference, IsDelegate = is_delegate });  // 加入GC隊列
                }
#if THREAD_SAFE || HOTFIX_ENABLE
            }
#endif
        }
        disposed = true;
    }
}

當disposeManagedResources為true時,直接調用ReleaseLuaBase釋放Lua對象

// ObjectTranslator.cs
public void ReleaseLuaBase(RealStatePtr L, int reference, bool is_delegate)
{
    if(is_delegate)
    {
        LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, reference);
        if (LuaAPI.lua_isnil(L, -1))
        {
            LuaAPI.lua_pop(L, 1);
        }
        else
        {
            LuaAPI.lua_pushvalue(L, -1);
            LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
            if (LuaAPI.lua_type(L, -1) == LuaTypes.LUA_TNUMBER && LuaAPI.xlua_tointeger(L, -1) == reference) //
            {
                //UnityEngine.Debug.LogWarning("release delegate ref = " + luaReference);
                LuaAPI.lua_pop(L, 1);// pop LUA_REGISTRYINDEX[func]
                LuaAPI.lua_pushnil(L);
                LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX); // LUA_REGISTRYINDEX[func] = nil
            }
            else //another Delegate ref the function before the GC tick
            {
                LuaAPI.lua_pop(L, 2); // pop LUA_REGISTRYINDEX[func] & func
            }
        }

        LuaAPI.lua_unref(L, reference);
        delegate_bridges.Remove(reference);
    }
    else
    {
        LuaAPI.lua_unref(L, reference);
    }
}

ReleaseLuaBase的主要任務是將Lua對象從Lua注冊表中移除,這樣Lua GC時發現該對象不再被引用,就可以進行回收了

// LuaEnv.cs
public void Tick()
{
#if THREAD_SAFE || HOTFIX_ENABLE
    lock (luaEnvLock)
    {
#endif
        var _L = L;
        lock (refQueue) 
        {
            while (refQueue.Count > 0)  // 遍歷GC隊列
            {
                GCAction gca = refQueue.Dequeue();
                translator.ReleaseLuaBase(_L, gca.Reference, gca.IsDelegate);
            }
        }
#if !XLUA_GENERAL
        last_check_point = translator.objects.Check(last_check_point, max_check_per_tick, object_valid_checker, translator.reverseMap);
#endif
#if THREAD_SAFE || HOTFIX_ENABLE
    }
#endif
}

當disposeManagedResources為false時,會將其加入GC隊列。當主動釋放Lua環境時,會遍歷GC隊列,再逐一調用ReleaseLuaBase進行釋放

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM