xLua地址:传送门
Xlua是啥?
2016年 腾讯推出的 一种 unity下 lua 编成的解决方案
基本概念介绍:
1.模块
模块就是一个 程序库,可以通过 require 加载,得到了一个表示 table的全局变量
这个table 就像一个命名空间,他的内容就是模块中导出的所有东西,比如:函数和常量
2.require 函数
Lua 提供了一个名为 require 的函数来加载模块。
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量
将模块中旧的函数替换成新的函数,这个新的函数可以放到一个Lua文件
3.upvalue与闭包
Lua函数可以被当成参数传递,可以被当成结果返回,在函数体中仍然可以定义内嵌函数
Lua闭包是Lua函数生成的数据对象。每个闭包可以有一个upvalue值,或者多个闭包共享一个upvalue数值
(如果函数F2定义在函数F1中,那么函数F2为F1的内嵌函数,F1为F2的外包函数)
内嵌函数可以访问外包函数已经创建的局部变量,而这些局部变量则被称为该内嵌函数的外部局部变量(或者upvalue)
4.require实现热更新
Lua 的 require(modelname) 把一个lua文件加载存放到 package.load[modelname]
当我们加载一个模块的时候,会先判断是否在package.loaded中已存在,如果存在就直接返回,不存在才加载,防止重复加载
通过AddLoader可以注册个回调,该回调参数是字符串
lua代码调用require时,参数将会透传给回调,回调中可以根据这个参数取加载指定文件
返回一个byte数组,如果为空则表示该loader找不到,否则则为lua文件内容
public class CustomLoader : Monobehaviour
{LuaEnv luaenv = null;void Start(){luaenv = new LuaEnv();luaenv.AddLoader((ref string filename) =>{return xxxx;});}
}
5.xLua使用流程
如图所示:
流程:
1.xLua文件配置:通过对C#的类与函数设置Hotfix标签。来标识需要支持热更的类和函数
a.打标签:
xLua用白名单来指明生成哪些代码,而白名单通过attribute来配置
(比如想从lua调用c#的某个类,希望生成适配代码,可以加一个 LuaCallCSharp 标签)
注意:会在il2cpp下增加不少代码量,不建议使用
[LuaCallCSharp]
public classA
{
}
b.静态列表
有时候不能给一个类型打标签(比如系统的API,没源码的库,或者实例化的泛型类型)
可以在一个静态类里声明一个静态字段,该字段的类型除 BlackList 和 AdditionalProperties 之外只要实现了 Ienumerable 就可以了
[LuaCallCSharp]
public static List<Type> my_lua_call_cs_list = new List<Type>()
{typeof(GameObject),typeof(Dictionary<string, int>)
};
c.动态列表
使用动态列表的方式,声明一个静态属性
比如对某命名空间下的所有类配置到 Hotfix 列表 or 全注入+排除特定 or 放到目录通过正则生成一个类列表等等
public static List<Type> by_property
{get{return (from type int Assembly.Load("Assembly-CSharp").GetTypes()where type.Namespace == "XXX"select type).ToList();}
}
注意:
上线后很难预测那些地方有bug,大部分时候需要把大多数类都囊括在内,然后把一些不需要通过热更新修改bug的部分排除在外(第三方库啥的),然后随着后续的迭代把日趋稳定的模块排除在外。
2.加载Lua文件
有三种方式
a.执行字符串
b.加载lua文件
c.自定义Loader(一般都用这种)
3.连接lua脚本与C#函数:xLua中有两种方式来实现Lua调用CS种德方法,一种是反射调用,一种是生成适配的代码
a.反射调用
[MonoPInvokeCallback(typeof(LuaCSFunction))]
static int FixCSFuction(RealStatePtr L)
{try{ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);int idx = LuaAPI.xlua_tointeger(L, LuaAPI.xlua_upvalueindex(1)); //获取闭包中的 upvalue 值LuaCSFunction func = (LuaCSFunction)translator.GetFixCSFunction(idx); // 获取对应的 LuaCSharpreturn func(L);// 执行}catch (System.Exception e){return LuaAPI.luaL_error(L, "c# exception in FixCSFunction : " + e);}
}
b.生成适配器代码(Generate Code)
在xLua中生成适配代码后会在Gen目录生成代码
根据C#类中需要支持热更的方法,生成对应的委托方法。
注册函数:
// 以偏向减少代码段的方式生成代码
public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func)
{idx = abs_idx(LuaAPI.lua_gettop(L), idx);// idx 就是指CLS_IDX,也就是SetCSTable设置的值LuaAPI.xlua_pushasciistring(L, name);// 压入方法名LuaAPI.lua_pushstdcallcfunction(L, func);// 压入c# wrapperLuaAPI.lua_rawset(L, idx);
}
public static void __Register(RealStatePtr L)
{ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);System.Type type = typeof(GameGlobal);//注册成员方法Utils.BeginObjectRegister(type, L, translator, 0, 0, 0, 0);Utils.EndObjectRegister(type, L, translator, null, null,null, null, null);//注册静态方法Utils.BeginClassRegister(type, L, __CreateInstance, 2, 26, 0);//注册回调Utils.RegisterFunc(L, Utils.CLS_IDX, "QuitGame", _m_QuitGame_xlua_st_);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "CoroutineTask", _g_get_CoroutineTask);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "TimeTask", _g_get_TimeTask);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "GameUpdater", _g_get_GameUpdater);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Color", _g_get_Color);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "GameState", _g_get_GameState);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "GameModule", _g_get_GameModule);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "ResourcesMng", _g_get_ResourcesMng);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Resources", _g_get_Resources);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "EVDispatcher", _g_get_EVDispatcher);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "UIModule", _g_get_UIModule);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "UIConfig", _g_get_UIConfig);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "UIController", _g_get_UIController);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "RedPoint", _g_get_RedPoint);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "UILayer", _g_get_UILayer);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Data", _g_get_Data);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Net", _g_get_Net);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Version", _g_get_Version);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "SDK", _g_get_SDK);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "HotUpdate", _g_get_HotUpdate);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "DB", _g_get_DB);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Sound", _g_get_Sound);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "HotFix", _g_get_HotFix);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "HeartPingManager", _g_get_HeartPingManager);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "ApplicateQuit", _g_get_ApplicateQuit);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "ApplicationPaused", _g_get_ApplicationPaused);Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "ApplicationFocused", _g_get_ApplicationFocused);Utils.EndClassRegister(type, L, translator);
}...
3.热更脚本注入:在C#脚本编译结束后,使用Mono提供的一套C#的API函数,对已经编译过的.Net体系生成的DLL文件进行修改
xLua对dll注入一些判断条件式来完成
Lua调用的行为很简单,就是检查对应类静态字段是否有 DelegateBridge 对象
(非常长的)源码如下:
bool injectMethod(MethodDefinition method, HotfixFlagInTool hotfixType){var type = method.DeclaringType;bool isFinalize = (method.Name == "Finalize" && method.IsSpecialName);MethodReference invoke = null;//__Gen_Delegate_Imp 方法引用int param_count = method.Parameters.Count + (method.IsStatic ? 0 : 1);if (!findHotfixDelegate(method, out invoke, hotfixType))// 根据返回值和参数个数类型,查找对应的委托方法{Error("can not find delegate for " + method.DeclaringType + "." + method.Name + "! try re-genertate code.");return false;}if (invoke == null){throw new Exception("unknow exception!");}#if XLUA_GENERALinvoke = injectAssembly.MainModule.ImportReference(invoke);
#elseinvoke = injectAssembly.MainModule.Import(invoke);
#endifFieldReference fieldReference = null;//插入的类静态字段,用来标记是否有对应的Lua注入VariableDefinition injection = null;//方法中的变量顶一bool isIntKey = hotfixType.HasFlag(HotfixFlagInTool.IntKey) && !type.HasGenericParameters && isTheSameAssembly;//isIntKey = !type.HasGenericParameters;if (!isIntKey){injection = new VariableDefinition(invoke.DeclaringType);//新建变量,加入方法体的变量组中method.Body.Variables.Add(injection);//获取这个方法对应的委托名,因为有重载方法存在//所以之前已经注入过的方法会在这边获取时计数+1//比如第一个重载获取的是__Hotfix0,那么下一个重载会是__Hotfix1,判断是否注入就是对应FieldReferencevar luaDelegateName = getDelegateName(method);if (luaDelegateName == null){Error("too many overload!");return false;}//创建对应的静态Field名字 就是上面收取道德luaDelegateNameFieldDefinition fieldDefinition = new FieldDefinition(luaDelegateName, Mono.Cecil.FieldAttributes.Static | Mono.Cecil.FieldAttributes.Private,invoke.DeclaringType);type.Fields.Add(fieldDefinition);fieldReference = fieldDefinition.GetGeneric();}bool ignoreValueType = hotfixType.HasFlag(HotfixFlagInTool.ValueTypeBoxing);//IL插入位置,目前定位的是方法的第一行var insertPoint = method.Body.Instructions[0];//获取IL处理器var processor = method.Body.GetILProcessor();//构造函数的处理逻辑先跳过这边不做分析if (method.IsConstructor){insertPoint = findNextRet(method.Body.Instructions, insertPoint);}Dictionary<Instruction, Instruction> originToNewTarget = new Dictionary<Instruction, Instruction>();HashSet<Instruction> noCheck = new HashSet<Instruction>();while (insertPoint != null){Instruction firstInstruction;if (isIntKey){firstInstruction = processor.Create(OpCodes.Ldc_I4, bridgeIndexByKey.Count);processor.InsertBefore(insertPoint, firstInstruction);processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, hotfixFlagGetter));}else{//创建第一条 IL 语句,获取类的静态Field压入方法栈用(之前luaDelegateName获取的字段)firstInstruction = processor.Create(OpCodes.Ldsfld, fieldReference);//插入 insertPoint 之前processor.InsertBefore(insertPoint, firstInstruction);//创建并插入IL,获取栈顶的值并压入到对应的变量中(之前创建的新建变量)processor.InsertBefore(insertPoint, processor.Create(OpCodes.Stloc, injection));//创建并插入 IL,压入变量体中的值到栈processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));}//创建跳转语句,为false时候直接跳转到insertPoint//OpCodes.Brfalse看起来是布尔值判断,其实也会判断是否为nullvar jmpInstruction = processor.Create(OpCodes.Brfalse, insertPoint);processor.InsertBefore(insertPoint, jmpInstruction);if (isIntKey){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldc_I4, bridgeIndexByKey.Count));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, delegateBridgeGetter));}else{//创建并插入IL,再次压入变量的值,因为上面判断之后,栈顶的值就会被弹出processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));}//后面就先不分析了。。。for (int i = 0; i < param_count; i++){if (i < ldargs.Length){processor.InsertBefore(insertPoint, processor.Create(ldargs[i]));}else if (i < 256){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg_S, (byte)i));}else{processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg, (short)i));}if (i == 0 && !method.IsStatic && type.IsValueType){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldobj, type));}if (ignoreValueType){TypeReference paramType;if (method.IsStatic){paramType = method.Parameters[i].ParameterType;}else{paramType = (i == 0) ? type : method.Parameters[i - 1].ParameterType;}if (paramType.IsValueType){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Box, paramType));}}}processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, invoke));if (!method.IsConstructor && !isFinalize){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ret));}if (!method.IsConstructor){break;}else{originToNewTarget[insertPoint] = firstInstruction;noCheck.Add(jmpInstruction);}insertPoint = findNextRet(method.Body.Instructions, insertPoint);}if (method.IsConstructor){fixBranch(processor, method.Body.Instructions, originToNewTarget, noCheck);}if (isFinalize){if (method.Body.ExceptionHandlers.Count == 0){throw new InvalidProgramException("Finalize has not try-catch? Type :" + method.DeclaringType);}method.Body.ExceptionHandlers[0].TryStart = method.Body.Instructions[0];}if (isIntKey){bridgeIndexByKey.Add(method);}return true;}bool injectGenericMethod(MethodDefinition method, HotfixFlagInTool hotfixType){//如果注入的是xlua所在之外的Assembly的话,不支持该方式if (!isTheSameAssembly){return true;}var type = method.DeclaringType;bool isFinalize = (method.Name == "Finalize" && method.IsSpecialName);bool isIntKey = hotfixType.HasFlag(HotfixFlagInTool.IntKey) && !type.HasGenericParameters;//isIntKey = !type.HasGenericParameters;FieldReference fieldReference = null;VariableDefinition injection = null;if (!isIntKey){var luaDelegateName = getDelegateName(method);if (luaDelegateName == null){Error("too many overload!");return false;}FieldDefinition fieldDefinition = new FieldDefinition(luaDelegateName, Mono.Cecil.FieldAttributes.Static | Mono.Cecil.FieldAttributes.Private,delegateBridgeType);type.Fields.Add(fieldDefinition);fieldReference = fieldDefinition.GetGeneric();}injection = new VariableDefinition(delegateBridgeType);method.Body.Variables.Add(injection);int param_start = method.IsStatic ? 0 : 1;int param_count = method.Parameters.Count + param_start;var insertPoint = method.Body.Instructions[0];var processor = method.Body.GetILProcessor();if (method.IsConstructor){insertPoint = findNextRet(method.Body.Instructions, insertPoint);}Dictionary<Instruction, Instruction> originToNewTarget = new Dictionary<Instruction, Instruction>();HashSet<Instruction> noCheck = new HashSet<Instruction>();while (insertPoint != null){Instruction firstInstruction;Instruction jmpInstruction;if (isIntKey){firstInstruction = processor.Create(OpCodes.Ldc_I4, bridgeIndexByKey.Count);processor.InsertBefore(insertPoint, firstInstruction);processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, hotfixFlagGetter));jmpInstruction = processor.Create(OpCodes.Brfalse, insertPoint);processor.InsertBefore(insertPoint, jmpInstruction);processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldc_I4, bridgeIndexByKey.Count));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, delegateBridgeGetter));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Stloc, injection));}else{firstInstruction = processor.Create(OpCodes.Ldsfld, fieldReference);processor.InsertBefore(insertPoint, firstInstruction);processor.InsertBefore(insertPoint, processor.Create(OpCodes.Stloc, injection));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));jmpInstruction = processor.Create(OpCodes.Brfalse, insertPoint);processor.InsertBefore(insertPoint, jmpInstruction);}processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, invokeSessionStart));TypeReference returnType = method.ReturnType;bool isVoid = returnType.FullName == "System.Void";int outCout = 0;for (int i = 0; i < param_count; i++){if (i == 0 && !method.IsStatic){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg_0));if (type.IsValueType){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldobj, method.DeclaringType.GetGeneric()));}processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, inParam.MakeGenericMethod(method.DeclaringType.GetGeneric())));}else{var param = method.Parameters[i - param_start];if (param.ParameterType.IsByReference){outCout++;}if (!param.IsOut){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));if (i < ldargs.Length){processor.InsertBefore(insertPoint, processor.Create(ldargs[i]));}else if (i < 256){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg_S, (byte)i));}else{processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg, (short)i));}var paramType = param.ParameterType;if (param.ParameterType.IsByReference){paramType = ((ByReferenceType)paramType).ElementType;if (paramType.IsValueType || paramType.IsGenericParameter){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldobj, paramType));}else{processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldind_Ref));}}if (i == param_count - 1 && param.CustomAttributes.Any(ca => ca.AttributeType.FullName == "System.ParamArrayAttribute")){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, inParams.MakeGenericMethod(((ArrayType)paramType).ElementType)));}else{processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, inParam.MakeGenericMethod(paramType)));}}}}int outStart = (isVoid ? 0 : 1);processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldc_I4, outCout + outStart));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, functionInvoke));int outPos = outStart;for (int i = 0; i < method.Parameters.Count; i++){if (method.Parameters[i].ParameterType.IsByReference){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldc_I4, outPos));int arg_pos = param_start + i;if (arg_pos < ldargs.Length){processor.InsertBefore(insertPoint, processor.Create(ldargs[arg_pos]));}else if (arg_pos < 256){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg_S, (byte)arg_pos));}else{processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg, (short)arg_pos));}processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, outParam.MakeGenericMethod(((ByReferenceType)method.Parameters[i].ParameterType).ElementType)));outPos++;}}processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));if (isVoid){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, invokeSessionEnd));}else{processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, invokeSessionEndWithResult.MakeGenericMethod(returnType)));}if (!method.IsConstructor && !isFinalize){processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ret));}if (!method.IsConstructor){break;}else{originToNewTarget[insertPoint] = firstInstruction;noCheck.Add(jmpInstruction);}insertPoint = findNextRet(method.Body.Instructions, insertPoint);}if (method.IsConstructor){fixBranch(processor, method.Body.Instructions, originToNewTarget, noCheck);}if (isFinalize){method.Body.ExceptionHandlers[0].TryStart = method.Body.Instructions[0];}if (isIntKey){bridgeIndexByKey.Add(method);}return true;}public void OutputIntKeyMapper(Stream output){using (StreamWriter writer = new StreamWriter(output)){writer.WriteLine("return {");var data = bridgeIndexByKey.Select((md, idx) => new { Method = md, Index = idx}).GroupBy(info => info.Method.DeclaringType).ToDictionary(group => group.Key, group =>{return group.GroupBy(info => info.Method.Name).ToDictionary(group_by_name => group_by_name.Key, group_by_name => group_by_name.Select(info => info.Index.ToString()).ToArray());});foreach(var kv in data){writer.WriteLine(" [\"" + kv.Key.FullName.Replace('/', '+') + "\"] = {");foreach(var kv2 in kv.Value){writer.WriteLine(" [\"" + kv2.Key + "\"] = {");writer.WriteLine(" " + string.Join(",", kv2.Value));writer.WriteLine(" },");}writer.WriteLine(" },");}writer.WriteLine("}");}}
4.执行热更脚本:通过脚本修改C#带有标签的类中的静态变量,把代码的执行路径修改到Lua脚本中
在完成生成代码和注入猴,只要在Lua中调用 xlua.hotfix 或 util.hotfix_ex 方法,就可以实现 c# 代码热更新
-- hotfix 和 hotfix_ex 的区别就是是否可以调用原 c# 代码,其实ex的实现也是调用了hotfix
xlua.hotfix = function(cs, field, func)if func == nil then func = false endlocal tbl = (type(field) == 'table') and field or {[field] = func}
-- 遍历需要 hotfix 的代码,key是方法名,v是对应的funcfor k,v in pairs(tbl) dolocal cflag = ''if k == '.ctor' thenclfag = 'c'k = 'ctor'endlocal f = type(v) == 'fucntion' and v or nil-- 调用 access 方法xlua.acces(cs, cflag..'__Hotfix0_'..k, f) --at least one-- 添加重载方法pcall(function()for i = 1, 99 doxlua.access(cs, cflag..'__Hotfix'..i..'_'..k, f)endend)end-- 设置私有访问xlua.private_accessible(cs)
end
其中额外的东西:
a).xlua.access 对应的c#代码是xLuaAccess代码处理
代码对应 XLua.StaticLuaCallbacks.cs 里面的 XLuaAccess() 方法
[MonoPInvokeCallback(typeof(LuaCSFunction))]
public static int XLuaAccess(RealStatePtr L)
{try{ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);Type type = getType(L, translator, 1);object obj = null;if (type == null && LuaAPI.lua_type(L, 1) == LuaTypes.LUA_TUSERDATA){obj = translator.SafeGetCSObj(L, 1);if (obj == null){return LuaAPI.luaL_error(L, "xlua.access, #1 parameter must a type/c# object/string");}type = obj.GetType();}if (type == null){return LuaAPI.luaL_error(L, "xlua.access, can not find c# type");}//将cflag..'__Hotfix0_'..k 转为fieldName,这个字段就是之前Inject时候创建的类的静态字段名string fieldName = LuaAPI.lua_tostring(L, 2);BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;if (LuaAPI.lua_gettop(L) > 2) // set{//获取函数名称var field = type.GetField(fieldName, bindingFlags);if (field != null){field.SetValue(obj, translator.GetObject(L, 3, field.FieldType));return 0;}var prop = type.GetProperty(fieldName, bindingFlags);if (prop != null){//修改当前函数名称prop.SetValue(obj, translator.GetObject(L, 3, prop.PropertyType), null);return 0;}}else{var field = type.GetField(fieldName, bindingFlags);if (field != null){translator.PushAny(L, field.GetValue(obj));return 1;}var prop = type.GetProperty(fieldName, bindingFlags);if (prop != null){translator.PushAny(L, prop.GetValue(obj, null));return 1;}}return LuaAPI.luaL_error(L, "xlua.access, no field " + fieldName);}catch (Exception e){return LuaAPI.luaL_error(L, "c# exception in xlua.access: " + e);}
}
b).创建修改函数对象
代码对应 XLua.ObjectTranslator.cs 里面的 CreateDelegateBridge () 方法
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);}//push idx对应的值,idx对应的值是Lua中的functionLuaAPI.lua_pushvalue(L, idx);//获取应用的引用idint reference = LuaAPI.luaL_ref(L);//再次压入idx对应的值,idx对应的值是Lua中的函数LuaAPI.lua_pushvalue(L, idx);//压入方法对应的引用idLuaAPI.lua_pushnumber(L, reference);//将栈顶的两个值存入全局变量表中,方便查询是否已经在lua中缓存LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);DelegateBridgeBase bridge;try{
#if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0if (!DelegateBridge.Gen_Flag){bridge = Activator.CreateInstance(delegate_birdge_type, new object[] { reference, luaEnv }) as DelegateBridgeBase;}else
#endif{//创建 DeleagteBridge,reference 对应了 lua 中修复的lua函数,在 Inject 时候 call 的方法会使用到这个函数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.AddDelegate(delegateType, ret);delegate_bridges[reference] = new WeakReference(bridge);return ret;}catch(Exception e){bridge.Dispose();throw e;}}
在调用hotfix猴,对应的修复类的静态字段会被设置成对应的DelegateBridge对象
c#执行到对应的被 xLua热更修复的代码时,会先执行注入的IL代码,检查是否有DelegateBridge,就会实际执行里面的方法,这样就执行了到了lua修复方法,实现了热更新