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的函数。

posted @ 2022-02-27 22:30  斯芬克斯  阅读(65)  评论(0编辑  收藏  举报