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.hotfix 或 util.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修复方法,实现了热更新