本文主要是探討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進行釋放

參考