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

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

2021-09-24 13:13:20 .NET開發

本文主要是探討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 = https://www.cnblogs.com/iwiniwin/archive/2021/09/24/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 = https://www.cnblogs.com/iwiniwin/archive/2021/09/24/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進行釋放

參考

  • 添加了中文注釋的xLua原始碼
  • 深入xLua實作原理之Lua如何呼叫C#
作者:iwiniwin 出處:http://www.cnblogs.com/iwiniwin/ 本文為博主原創文章,轉載請附上原文出處鏈接和本宣告,

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

標籤:.NET技术

上一篇:Dapr + .NET Core實戰(三)狀態管理

下一篇:記一次 .NET 某上市工業智造 CPU+記憶體+掛死 三高分析

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 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
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more