xlua热修复原理
xlua热修复的使用方法:
功能开启步骤:
(1)特性宏定义:HOTFIX_ENABLE;
平时不开,build版本时动态打开。
(2)执行:XLua/Generate Code,生成各种辅助代码;
(3)执行:XLua/Hotfix Inject In Editor,对DLL进行注入。
手机版本build时自动进行;
Editor下手动调用;
打印hotfix inject finish!才算成功。
修复函数:
xlua.hotfix(class, [method_name], fix)
util.hotfix_ex(class, method_name, fix)
增强版,速度略慢
待修复C#代码:
public class CSharpClass { public int iValue = 100; public string strValue = ""; public CSharpClass() { iValue = 1; strValue = "default"; } public void Print() { UnityEngine.Debug.LogError($"call print: {iValue}, {strValue}"); } public int Add(int v) { iValue += v; return iValue; } }
指定哪些类型需要进行热修复,放在Editor目录下:
public static class HotfixCfg { [XLua.Hotfix] public static List<Type> by_field = new List<Type>() { typeof(CSharpClass), }; }
lua代码,其中使用了xlua.hotfix函数进行C#函数修改:
local function Print_hotfix(self) print("Print_hotfix") end -- 热修复 xlua.hotfix(CS.CSharpClass, 'Print', Print_hotfix) local obj = CS.CSharpClass() obj.strValue = "strFromLua" obj:Print() -- 需要手动置空,不然会有引用泄露 xlua.hotfix(CS.CSharpClass, 'Print', nil)
热修复原理分析:
可以看到需要热修复的函数的IL代码添加了一些插桩代码,如果hotfix0_xxx不为空则走hotfix,否则保持原逻辑。
首先看一下xlua.hotfix这个lua函数的实现:
LuaEnv.cs line 553 定义了xlua.hotfix函数:该函数所做的事情就是cs.__Hotfix0_funcname = func
xlua.hotfix = function(cs, field, func) if func == nil then func = false end local tbl = (type(field) == 'table') and field or {[field] = func} for k, v in pairs(tbl) do local cflag = '' if k == '.ctor' then cflag = '_c' k = 'ctor' end local f = type(v) == 'function' and v or nil xlua.access(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
ObjectTranslator.cs的line 690 定义了xlua.access函数的实现:
LuaAPI.xlua_pushasciistring(L, "access"); LuaAPI.lua_pushstdcallcfunction(L, StaticLuaCallbacks.XLuaAccess); LuaAPI.lua_rawset(L, -3); LuaAPI.xlua_pushasciistring(L, "private_accessible"); LuaAPI.lua_pushstdcallcfunction(L, StaticLuaCallbacks.XLuaPrivateAccessible); LuaAPI.lua_rawset(L, -3);
StaticLuaCallbacks.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; // ...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; } } // ... }
实现中通过反射来设置csharp class的静态变量的值。(lua的基本操作都是基于堆栈进行的,上述代码看不懂的可以在帖子下面留言。)
从生成的IL代码可以看到,构造函数和普通成员函数的修复行为也不一样。
普通成员函数:使用lua函数覆盖;
构造函数:先执行C#原有的构造函数,再执行lua的函数。