找回密码
 立即注册
查看: 289|回复: 0

xLua介绍

[复制链接]
发表于 2021-8-14 21:45 | 显示全部楼层 |阅读模式
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文件内容
  1. publicclassCustomLoader:Monobehaviour{LuaEnv luaenv =null;voidStart(){
  2.     luaenv =newLuaEnv();
  3.     luaenv.AddLoader((refstring filename)=>{return xxxx;});}}
复制代码
5.xLua使用流程
如图所示:


流程:
1.xLua文件配置:通过对C#的类与函数设置Hotfix标签。来标识需要支持热更的类和函数
a.打标签:
xLua用白名单来指明生成哪些代码,而白名单通过attribute来配置
(比如想从lua调用c#的某个类,希望生成适配代码,可以加一个 LuaCallCSharp 标签)
注意:会在il2cpp下增加不少代码量,不建议使用
  1. [LuaCallCSharp]public classA
  2. {}
复制代码
b.静态列表
有时候不能给一个类型打标签(比如系统的API,没源码的库,或者实例化的泛型类型)
可以在一个静态类里声明一个静态字段,该字段的类型除 BlackList 和 AdditionalProperties 之外只要实现了 Ienumerable 就可以了
  1. [LuaCallCSharp]
  2. public static List<Type> my_lua_call_cs_list = new List<Type>()
  3. {
  4.   typeof(GameObject),
  5.   typeof(Dictionary<string, int>)
  6. };
复制代码
c.动态列表
使用动态列表的方式,声明一个静态属性
比如对某命名空间下的所有类配置到 Hotfix 列表 or 全注入+排除特定 or 放到目录通过正则生成一个类列表等等
  1. publicstatic List<Type> by_property
  2. {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.反射调用
  1. [MonoPInvokeCallback(typeof(LuaCSFunction))]staticintFixCSFuction(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);// 获取对应的 LuaCSharpreturnfunc(L);// 执行}catch(System.Exception e){return LuaAPI.luaL_error(L,"c# exception in FixCSFunction : "+ e);}}
复制代码
b.生成适配器代码(Generate Code)
在xLua中生成适配代码后会在Gen目录生成代码
根据C#类中需要支持热更的方法,生成对应的委托方法。
注册函数:
  1. // 以偏向减少代码段的方式生成代码publicstaticvoidRegisterFunc(RealStatePtr L,int idx,string name,LuaCSFunction func){
  2.         idx =abs_idx(LuaAPI.lua_gettop(L), idx);// idx 就是指CLS_IDX,也就是SetCSTable设置的值
  3.         LuaAPI.xlua_pushasciistring(L, name);// 压入方法名
  4.         LuaAPI.lua_pushstdcallcfunction(L, func);// 压入c# wrapper
  5.         LuaAPI.lua_rawset(L, idx);}
复制代码
  1. public static void __Register(RealStatePtr L)
  2. {
  3.         ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
  4.         System.Type type = typeof(GameGlobal);
  5.     //注册成员方法
  6.         Utils.BeginObjectRegister(type, L, translator, 0, 0, 0, 0);
  7.        
  8.         Utils.EndObjectRegister(type, L, translator, null, null,
  9.             null, null, null);
  10.     //注册静态方法
  11.     Utils.BeginClassRegister(type, L, __CreateInstance, 2, 26, 0);
  12.         //注册回调
  13.         Utils.RegisterFunc(L, Utils.CLS_IDX, "QuitGame", _m_QuitGame_xlua_st_);
  14.    
  15.         Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "CoroutineTask", _g_get_CoroutineTask);
  16.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "TimeTask", _g_get_TimeTask);
  17.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "GameUpdater", _g_get_GameUpdater);
  18.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Color", _g_get_Color);
  19.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "GameState", _g_get_GameState);
  20.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "GameModule", _g_get_GameModule);
  21.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "ResourcesMng", _g_get_ResourcesMng);
  22.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Resources", _g_get_Resources);
  23.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "EVDispatcher", _g_get_EVDispatcher);
  24.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "UIModule", _g_get_UIModule);
  25.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "UIConfig", _g_get_UIConfig);
  26.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "UIController", _g_get_UIController);
  27.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "RedPoint", _g_get_RedPoint);
  28.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "UILayer", _g_get_UILayer);
  29.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Data", _g_get_Data);
  30.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Net", _g_get_Net);
  31.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Version", _g_get_Version);
  32.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "SDK", _g_get_SDK);
  33.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "HotUpdate", _g_get_HotUpdate);
  34.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "DB", _g_get_DB);
  35.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "Sound", _g_get_Sound);
  36.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "HotFix", _g_get_HotFix);
  37.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "HeartPingManager", _g_get_HeartPingManager);
  38.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "ApplicateQuit", _g_get_ApplicateQuit);
  39.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "ApplicationPaused", _g_get_ApplicationPaused);
  40.     Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "ApplicationFocused", _g_get_ApplicationFocused);
  41.         Utils.EndClassRegister(type, L, translator);
  42. }
  43. ...
复制代码
3.热更脚本注入:在C#脚本编译结束后,使用Mono提供的一套C#的API函数,对已经编译过的.Net体系生成的DLL文件进行修改
xLua对dll注入一些判断条件式来完成
Lua调用的行为很简单,就是检查对应类静态字段是否有 DelegateBridge 对象
(非常长的)源码如下:
  1. boolinjectMethod(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.");returnfalse;}if(invoke ==null){thrownewException("unknow exception!");}#if XLUA_GENERAL
  2.             invoke = injectAssembly.MainModule.ImportReference(invoke);#else
  3.             invoke = 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){
  4.                 injection =newVariableDefinition(invoke.DeclaringType);//新建变量,加入方法体的变量组中
  5.                 method.Body.Variables.Add(injection);//获取这个方法对应的委托名,因为有重载方法存在//所以之前已经注入过的方法会在这边获取时计数+1//比如第一个重载获取的是__Hotfix0,那么下一个重载会是__Hotfix1,判断是否注入就是对应FieldReferencevar luaDelegateName =getDelegateName(method);if(luaDelegateName ==null){Error("too many overload!");returnfalse;}//创建对应的静态Field名字 就是上面收取道德luaDelegateNameFieldDefinition fieldDefinition =newFieldDefinition(luaDelegateName, Mono.Cecil.FieldAttributes.Static | Mono.Cecil.FieldAttributes.Private,
  6.                     invoke.DeclaringType);
  7.                 type.Fields.Add(fieldDefinition);
  8.                 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){
  9.                 insertPoint =findNextRet(method.Body.Instructions, insertPoint);}
  10.             Dictionary<Instruction, Instruction> originToNewTarget =newDictionary<Instruction,Instruction>();
  11.             HashSet<Instruction> noCheck =newHashSet<Instruction>();while(insertPoint !=null){Instruction firstInstruction;if(isIntKey){
  12.                     firstInstruction = processor.Create(OpCodes.Ldc_I4, bridgeIndexByKey.Count);
  13.                     processor.InsertBefore(insertPoint, firstInstruction);
  14.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, hotfixFlagGetter));}else{//创建第一条 IL 语句,获取类的静态Field压入方法栈用(之前luaDelegateName获取的字段)
  15.                     firstInstruction = processor.Create(OpCodes.Ldsfld, fieldReference);//插入 insertPoint 之前
  16.                     processor.InsertBefore(insertPoint, firstInstruction);//创建并插入IL,获取栈顶的值并压入到对应的变量中(之前创建的新建变量)
  17.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Stloc, injection));//创建并插入 IL,压入变量体中的值到栈
  18.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));}//创建跳转语句,为false时候直接跳转到insertPoint//OpCodes.Brfalse看起来是布尔值判断,其实也会判断是否为nullvar jmpInstruction = processor.Create(OpCodes.Brfalse, insertPoint);
  19.                 processor.InsertBefore(insertPoint, jmpInstruction);if(isIntKey){
  20.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldc_I4, bridgeIndexByKey.Count));
  21.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, delegateBridgeGetter));}else{//创建并插入IL,再次压入变量的值,因为上面判断之后,栈顶的值就会被弹出
  22.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));}//后面就先不分析了。。。for(int i =0; i < param_count; i++){if(i < ldargs.Length){
  23.                         processor.InsertBefore(insertPoint, processor.Create(ldargs[i]));}elseif(i <256){
  24.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg_S,(byte)i));}else{
  25.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg,(short)i));}if(i ==0&&!method.IsStatic && type.IsValueType){
  26.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldobj, type));}if(ignoreValueType){TypeReference paramType;if(method.IsStatic){
  27.                             paramType = method.Parameters[i].ParameterType;}else{
  28.                             paramType =(i ==0)? type : method.Parameters[i -1].ParameterType;}if(paramType.IsValueType){
  29.                             processor.InsertBefore(insertPoint, processor.Create(OpCodes.Box, paramType));}}}
  30.                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, invoke));if(!method.IsConstructor &&!isFinalize){
  31.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ret));}if(!method.IsConstructor){break;}else{
  32.                     originToNewTarget[insertPoint]= firstInstruction;
  33.                     noCheck.Add(jmpInstruction);}
  34.                 insertPoint =findNextRet(method.Body.Instructions, insertPoint);}if(method.IsConstructor){fixBranch(processor, method.Body.Instructions, originToNewTarget, noCheck);}if(isFinalize){if(method.Body.ExceptionHandlers.Count ==0){thrownewInvalidProgramException("Finalize has not try-catch? Type :"+ method.DeclaringType);}
  35.                 method.Body.ExceptionHandlers[0].TryStart = method.Body.Instructions[0];}if(isIntKey){
  36.                 bridgeIndexByKey.Add(method);}returntrue;}boolinjectGenericMethod(MethodDefinition method,HotfixFlagInTool hotfixType){//如果注入的是xlua所在之外的Assembly的话,不支持该方式if(!isTheSameAssembly){returntrue;}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!");returnfalse;}FieldDefinition fieldDefinition =newFieldDefinition(luaDelegateName, Mono.Cecil.FieldAttributes.Static | Mono.Cecil.FieldAttributes.Private,
  37.                     delegateBridgeType);
  38.                 type.Fields.Add(fieldDefinition);
  39.                 fieldReference = fieldDefinition.GetGeneric();}
  40.             injection =newVariableDefinition(delegateBridgeType);
  41.             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){
  42.                 insertPoint =findNextRet(method.Body.Instructions, insertPoint);}
  43.             Dictionary<Instruction, Instruction> originToNewTarget =newDictionary<Instruction,Instruction>();
  44.             HashSet<Instruction> noCheck =newHashSet<Instruction>();while(insertPoint !=null){Instruction firstInstruction;Instruction jmpInstruction;if(isIntKey){
  45.                     firstInstruction = processor.Create(OpCodes.Ldc_I4, bridgeIndexByKey.Count);
  46.                     processor.InsertBefore(insertPoint, firstInstruction);
  47.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, hotfixFlagGetter));
  48.                     jmpInstruction = processor.Create(OpCodes.Brfalse, insertPoint);
  49.                     processor.InsertBefore(insertPoint, jmpInstruction);
  50.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldc_I4, bridgeIndexByKey.Count));
  51.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Call, delegateBridgeGetter));
  52.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Stloc, injection));}else{
  53.                     firstInstruction = processor.Create(OpCodes.Ldsfld, fieldReference);
  54.                     processor.InsertBefore(insertPoint, firstInstruction);
  55.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Stloc, injection));
  56.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));
  57.                     jmpInstruction = processor.Create(OpCodes.Brfalse, insertPoint);
  58.                     processor.InsertBefore(insertPoint, jmpInstruction);}
  59.                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));
  60.                 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){
  61.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));
  62.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg_0));if(type.IsValueType){
  63.                             processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldobj, method.DeclaringType.GetGeneric()));}
  64.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, inParam.MakeGenericMethod(method.DeclaringType.GetGeneric())));}else{var param = method.Parameters[i - param_start];if(param.ParameterType.IsByReference){
  65.                             outCout++;}if(!param.IsOut){
  66.                             processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));if(i < ldargs.Length){
  67.                                 processor.InsertBefore(insertPoint, processor.Create(ldargs[i]));}elseif(i <256){
  68.                                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg_S,(byte)i));}else{
  69.                                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg,(short)i));}var paramType = param.ParameterType;if(param.ParameterType.IsByReference){
  70.                                 paramType =((ByReferenceType)paramType).ElementType;if(paramType.IsValueType || paramType.IsGenericParameter){
  71.                                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldobj, paramType));}else{
  72.                                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldind_Ref));}}if(i == param_count -1&& param.CustomAttributes.Any(ca => ca.AttributeType.FullName =="System.ParamArrayAttribute")){
  73.                                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, inParams.MakeGenericMethod(((ArrayType)paramType).ElementType)));}else{
  74.                                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, inParam.MakeGenericMethod(paramType)));}}}}int outStart =(isVoid ?0:1);
  75.                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));
  76.                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldc_I4, outCout + outStart));
  77.                 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){
  78.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));
  79.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldc_I4, outPos));int arg_pos = param_start + i;if(arg_pos < ldargs.Length){
  80.                             processor.InsertBefore(insertPoint, processor.Create(ldargs[arg_pos]));}elseif(arg_pos <256){
  81.                             processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg_S,(byte)arg_pos));}else{
  82.                             processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldarg,(short)arg_pos));}
  83.                         processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, outParam.MakeGenericMethod(((ByReferenceType)method.Parameters[i].ParameterType).ElementType)));
  84.                         outPos++;}}
  85.                 processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ldloc, injection));if(isVoid){
  86.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, invokeSessionEnd));}else{
  87.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Callvirt, invokeSessionEndWithResult.MakeGenericMethod(returnType)));}if(!method.IsConstructor &&!isFinalize){
  88.                     processor.InsertBefore(insertPoint, processor.Create(OpCodes.Ret));}if(!method.IsConstructor){break;}else{
  89.                     originToNewTarget[insertPoint]= firstInstruction;
  90.                     noCheck.Add(jmpInstruction);}
  91.                 insertPoint =findNextRet(method.Body.Instructions, insertPoint);}if(method.IsConstructor){fixBranch(processor, method.Body.Instructions, originToNewTarget, noCheck);}if(isFinalize){
  92.                 method.Body.ExceptionHandlers[0].TryStart = method.Body.Instructions[0];}if(isIntKey){
  93.                 bridgeIndexByKey.Add(method);}returntrue;}publicvoidOutputIntKeyMapper(Stream output){using(StreamWriter writer =newStreamWriter(output)){
  94.                 writer.WriteLine("return {");var data = bridgeIndexByKey
  95.                     .Select((md, idx)=>new{ Method = md, Index = idx}).GroupBy(info => info.Method.DeclaringType).ToDictionary(group=>group.Key,group=>{returngroup.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){
  96.                     writer.WriteLine("    [""+ kv.Key.FullName.Replace('/','+')+""] = {");foreach(var kv2 in kv.Value){
  97.                         writer.WriteLine("        [""+ kv2.Key +""] = {");
  98.                         writer.WriteLine("            "+string.Join(",", kv2.Value));
  99.                         writer.WriteLine("        },");}
  100.                     writer.WriteLine("    },");}
  101.                 writer.WriteLine("}");}}
复制代码
4.执行热更脚本:通过脚本修改C#带有标签的类中的静态变量,把代码的执行路径修改到Lua脚本中
在完成生成代码和注入猴,只要在Lua中调用 xlua.hotfixutil.hotfix_ex 方法,就可以实现 c# 代码热更新
  1. -- hotfix 和 hotfix_ex 的区别就是是否可以调用原 c# 代码,其实ex的实现也是调用了hotfix
  2. xlua.hotfix =function(cs, field, func)if func ==nilthen func =falseendlocal tbl =(type(field)=='table')and field or{[field]= func}-- 遍历需要 hotfix 的代码,key是方法名,v是对应的funcfor k,v inpairs(tbl)dolocal cflag =''if k =='.ctor'then
  3.         clfag ='c'
  4.         k ='ctor'endlocal f =type(v)=='fucntion'and v ornil-- 调用 access 方法
  5.     xlua.acces(cs, cflag..'__Hotfix0_'..k, f)--at least one-- 添加重载方法pcall(function()for i =1,99do
  6.                     xlua.access(cs, cflag..'__Hotfix'..i..'_'..k, f)endend)end-- 设置私有访问
  7.   xlua.private_accessible(cs)end
复制代码
其中额外的东西:
a).xlua.access 对应的c#代码是xLuaAccess代码处理
代码对应 XLua.StaticLuaCallbacks.cs 里面的 XLuaAccess() 方法
  1. [MonoPInvokeCallback(typeof(LuaCSFunction))]publicstaticintXLuaAccess(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){
  2.             obj = translator.SafeGetCSObj(L,1);if(obj ==null){return LuaAPI.luaL_error(L,"xlua.access, #1 parameter must a type/c# object/string");}
  3.             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){
  4.                 field.SetValue(obj, translator.GetObject(L,3, field.FieldType));return0;}var prop = type.GetProperty(fieldName, bindingFlags);if(prop !=null){//修改当前函数名称
  5.                 prop.SetValue(obj, translator.GetObject(L,3, prop.PropertyType),null);return0;}}else{var field = type.GetField(fieldName, bindingFlags);if(field !=null){
  6.                 translator.PushAny(L, field.GetValue(obj));return1;}var prop = type.GetProperty(fieldName, bindingFlags);if(prop !=null){
  7.                 translator.PushAny(L, prop.GetValue(obj,null));return1;}}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 () 方法
  1. publicobjectCreateDelegateBridge(RealStatePtr L,Type delegateType,int idx){
  2.             LuaAPI.lua_pushvalue(L, idx);
  3.             LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);if(!LuaAPI.lua_isnil(L,-1)){int referenced = LuaAPI.xlua_tointeger(L,-1);
  4.                 LuaAPI.lua_pop(L,1);if(delegate_bridges[referenced].IsAlive){if(delegateType ==null){return delegate_bridges[referenced].Target;}DelegateBridgeBase exist_bridge = delegate_bridges[referenced].Targetas DelegateBridgeBase;Delegate exist_delegate;if(exist_bridge.TryGetDelegate(delegateType,out exist_delegate)){return exist_delegate;}else{
  5.                         exist_delegate =getDelegate(exist_bridge, delegateType);
  6.                         exist_bridge.AddDelegate(delegateType, exist_delegate);return exist_delegate;}}}else{
  7.                 LuaAPI.lua_pop(L,1);}//push idx对应的值,idx对应的值是Lua中的function
  8.             LuaAPI.lua_pushvalue(L, idx);//获取应用的引用idint reference = LuaAPI.luaL_ref(L);//再次压入idx对应的值,idx对应的值是Lua中的函数
  9.             LuaAPI.lua_pushvalue(L, idx);//压入方法对应的引用id
  10.             LuaAPI.lua_pushnumber(L, reference);//将栈顶的两个值存入全局变量表中,方便查询是否已经在lua中缓存
  11.             LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);DelegateBridgeBase bridge;try{#if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0if(!DelegateBridge.Gen_Flag){
  12.                     bridge = Activator.CreateInstance(delegate_birdge_type,newobject[]{ reference, luaEnv })as DelegateBridgeBase;}else#endif{//创建 DeleagteBridge,reference 对应了 lua 中修复的lua函数,在 Inject 时候 call 的方法会使用到这个函数
  13.                     bridge =newDelegateBridge(reference, luaEnv);}}catch(Exception e){
  14.                 LuaAPI.lua_pushvalue(L, idx);
  15.                 LuaAPI.lua_pushnil(L);
  16.                 LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);
  17.                 LuaAPI.lua_pushnil(L);
  18.                 LuaAPI.xlua_rawseti(L, LuaIndexes.LUA_REGISTRYINDEX, reference);throw e;}if(delegateType ==null){
  19.                 delegate_bridges[reference]=newWeakReference(bridge);return bridge;}try{var ret =getDelegate(bridge, delegateType);
  20.                 bridge.AddDelegate(delegateType, ret);
  21.                 delegate_bridges[reference]=newWeakReference(bridge);return ret;}catch(Exception e){
  22.                 bridge.Dispose();throw e;}}
复制代码
在调用hotfix猴,对应的修复类的静态字段会被设置成对应的DelegateBridge对象
c#执行到对应的被 xLua热更修复的代码时,会先执行注入的IL代码,检查是否有DelegateBridge,就会实际执行里面的方法,这样就执行了到了lua修复方法,实现了热更新

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 11:44 , Processed in 0.108556 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表