xLua介绍

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);  // 获取对应的 LuaCSharp
        return 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# wrapper
    LuaAPI.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_GENERAL
            invoke = injectAssembly.MainModule.ImportReference(invoke);
#else
            invoke = injectAssembly.MainModule.Import(invoke);
#endif

            FieldReference 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,判断是否注入就是对应FieldReference
                var luaDelegateName = getDelegateName(method);
                if (luaDelegateName == null)
                {
                    Error("too many overload!");
                    return false;
                }

                //创建对应的静态Field名字 就是上面收取道德luaDelegateName
                FieldDefinition 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看起来是布尔值判断,其实也会判断是否为null
                var 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.hotfixutil.hotfix_ex 方法,就可以实现 c# 代码热更新

-- hotfix 和 hotfix_ex 的区别就是是否可以调用原 c# 代码,其实ex的实现也是调用了hotfix
xlua.hotfix = function(cs, field, func)
  if func == nil then func = false end
  local tbl = (type(field) == 'table') and field or {[field] = func}
-- 遍历需要 hotfix 的代码,key是方法名,v是对应的func
  for k,v in pairs(tbl) do
    local cflag = ''
    if k == '.ctor' then
        clfag = 'c'
        k = 'ctor'
    end
    local 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 do
                    xlua.access(cs, cflag..'__Hotfix'..i..'_'..k, f)
                end
            end)
  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中的function
            LuaAPI.lua_pushvalue(L, idx);
            //获取应用的引用id
            int reference = LuaAPI.luaL_ref(L);
            //再次压入idx对应的值,idx对应的值是Lua中的函数
            LuaAPI.lua_pushvalue(L, idx);
            //压入方法对应的引用id
            LuaAPI.lua_pushnumber(L, reference);
            //将栈顶的两个值存入全局变量表中,方便查询是否已经在lua中缓存
            LuaAPI.lua_rawset(L, LuaIndexes.LUA_REGISTRYINDEX);
            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;
                }
                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修复方法,实现了热更新

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342