本文主要是探討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/p/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/p/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#
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/302605.html
標籤:C#
