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

xLua源码解析——初始化xLua

[复制链接]
发表于 2022-6-22 15:04 | 显示全部楼层 |阅读模式
环境:xlua_v2.1.15,lua5.4.1

XLua.LuaEnv luaenv = new XLua.LuaEnv();
luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
luaenv.Dispose();
这是xLua指引的快速入门例子,实例化LueEnv,执行lua代码,释放LueEnv实例。
下面看看LuaEnv实例化做了什么事情。
LuaEnv实例化

结合注释,先过一遍源码
public LuaEnv()
{
    // 判断xLua的c代码版本与c#代码版本是否一致
    if (LuaAPI.xlua_get_lib_version() != LIB_VERSION_EXPECT)
    {
        throw new InvalidProgramException("wrong lib version expect:"
            + LIB_VERSION_EXPECT + " but got:" + LuaAPI.xlua_get_lib_version());
    }

#if THREAD_SAFE || HOTFIX_ENABLE
    lock(luaEnvLock)
#endif
    {
        // 记录lua注册表索引,这是一个很重要的表,后续会大量使用
        LuaIndexes.LUA_REGISTRYINDEX = LuaAPI.xlua_get_registry_index();
#if GEN_CODE_MINIMIZE
        LuaAPI.xlua_set_csharp_wrapper_caller(InternalGlobals.CSharpWrapperCallerPtr);
#endif
        // Create State
        rawL = LuaAPI.luaL_newstate();

        //Init Base Libs
        LuaAPI.luaopen_xlua(rawL);// 注册xlua的c方法库
        LuaAPI.luaopen_i64lib(rawL);// 注册uint64库

        translator = new ObjectTranslator(this, rawL);// 用于lua与c#之间传值
        translator.createFunctionMetatable(rawL);// 创建LuaCSFunction的元表

        translator.OpenLib(rawL);// 注册xlua的cs方法库
        ObjectTranslatorPool.Instance.Add(rawL, translator);

        LuaAPI.lua_atpanic(rawL, StaticLuaCallbacks.Panic);// 设置lua异常处理函数

#if !XLUA_GENERAL
        LuaAPI.lua_pushstdcallcfunction(rawL, StaticLuaCallbacks.Print);// 设置print函数
        if (0 != LuaAPI.xlua_setglobal(rawL, "print"))
        {
            throw new Exception("call xlua_setglobal fail!");
        }
#endif

        //template engine lib register
        TemplateEngine.LuaTemplate.OpenLib(rawL);// 注册template库
        // 添加require时用到的查找器
        AddSearcher(StaticLuaCallbacks.LoadBuiltinLib, 2); // just after the preload searcher
        AddSearcher(StaticLuaCallbacks.LoadFromCustomLoaders, 3);
#if !XLUA_GENERAL
        AddSearcher(StaticLuaCallbacks.LoadFromResource, 4);
        AddSearcher(StaticLuaCallbacks.LoadFromStreamingAssetsPath, -1);
#endif
        DoString(init_xlua, "Init");
        init_xlua = null;

        // 添加内置库注册函数,等需要这些库的时候才调用方法去加载,
        // 在上面StaticLuaCallbacks.LoadBuiltinLib函数的逻辑中加载
#if (!UNITY_SWITCH && !UNITY_WEBGL) || UNITY_EDITOR
        AddBuildin("socket.core", StaticLuaCallbacks.LoadSocketCore);
        AddBuildin("socket", StaticLuaCallbacks.LoadSocketCore);
#endif

        AddBuildin("CS", StaticLuaCallbacks.LoadCS);

        // local mt = {__index = StaticLuaCallbacks.MetaFuncIndex}
        LuaAPI.lua_newtable(rawL); //metatable of indexs and newindexs functions
        LuaAPI.xlua_pushasciistring(rawL, "__index");
        LuaAPI.lua_pushstdcallcfunction(rawL, StaticLuaCallbacks.MetaFuncIndex);
        LuaAPI.lua_rawset(rawL, -3);

        // register[Utils.LuaIndexsFieldName] = setmetatable({}, mt)
        // register[Utils.LuaNewIndexsFieldName] = setmetatable({}, mt)
        // register[Utils.LuaClassIndexsFieldName] = setmetatable({}, mt)
        // register[Utils.LuaClassNewIndexsFieldName] = setmetatable({}, mt)
        LuaAPI.xlua_pushasciistring(rawL, Utils.LuaIndexsFieldName);
        LuaAPI.lua_newtable(rawL);
        LuaAPI.lua_pushvalue(rawL, -3);
        LuaAPI.lua_setmetatable(rawL, -2);
        LuaAPI.lua_rawset(rawL, LuaIndexes.LUA_REGISTRYINDEX);
        ......

        LuaAPI.lua_pop(rawL, 1); // pop metatable of indexs and newindexs functions

        // register[MAIN_SHREAD] = rawL
        LuaAPI.xlua_pushasciistring(rawL, MAIN_SHREAD);
        LuaAPI.lua_pushthread(rawL);
        LuaAPI.lua_rawset(rawL, LuaIndexes.LUA_REGISTRYINDEX);

        // register[CSHARP_NAMESPACE] = CS
        LuaAPI.xlua_pushasciistring(rawL, CSHARP_NAMESPACE);
        if (0 != LuaAPI.xlua_getglobal(rawL, "CS"))
        {
            throw new Exception("get CS fail!");
        }
        LuaAPI.lua_rawset(rawL, LuaIndexes.LUA_REGISTRYINDEX);

#if !XLUA_GENERAL && (!UNITY_WSA || UNITY_EDITOR)
        // 设置类型的别名
        translator.Alias(typeof(Type), "System.MonoType");
#endif
        // LuaEnv._G持有lua的_G表
        if (0 != LuaAPI.xlua_getglobal(rawL, "_G"))
        {
            throw new Exception("get _G fail!");
        }
        translator.Get(rawL, -1, out _G);
        LuaAPI.lua_pop(rawL, 1);

        errorFuncRef = LuaAPI.get_error_func_ref(rawL);

        // 调用所有初始化器,绑定类型与构造table的方法
        if (initers != null)
        {
            for (int i = 0; i < initers.Count; i++)
            {
                initers(this, translator);
            }
        }

        translator.CreateArrayMetatable(rawL);
        translator.CreateDelegateMetatable(rawL);
        translator.CreateEnumerablePairs(rawL);
    }
}
总结一下,构造方法执行了以下行为:

  • 实例化lua主线程rawL
  • 注册基础库xlua(包含c和cs的方法)、uint64、template
  • 实例化ObjectTranslator,并创建函数、数组、委托的元表,枚举迭代的函数
  • 设置print、panic函数
  • 添加require时查找lua文件的查找器
  • 调用init_xlua的lua代码
  • 创建表:LuaIndexs、LuaNewIndexs、LuaClassIndexs、LuaClassNewIndexs,这4个表用于记录后续C#的type与index和newindex的映射关系。4个表+CS表+rawL,都放到注册表中。
  • 调用所有初始化器,记录C#的type与创建对应table的函数的映射关系,后续首次访问某个类型时,就会调用函数构造出table给lua层使用。这些函数就是由xlua的general code生成的。
ObjectTranslator实例化

在LuaEnv实例化的时候,同时也会实例化ObjectTranslator,这个类的作用是将lua数据栈的值转换为c#类型的值和将c#类型的值压入lua的数据栈,是lua和c#交互的桥梁。
public ObjectTranslator(LuaEnv luaenv, RealStatePtr L)
{
#if XLUA_GENERAL || (UNITY_WSA && !UNITY_EDITOR)
    var dumb_field = typeof(ObjectTranslator).GetField("s_gen_reg_dumb_obj", BindingFlags.Static| BindingFlags.DeclaredOnly | BindingFlags.NonPublic);
    if (dumb_field != null)
    {
        dumb_field.GetValue(null);
    }
#endif
    // 记录用到的程序集,后续用于通过名字查找类型type
    assemblies = new List<Assembly>();

#if (UNITY_WSA && !ENABLE_IL2CPP) && !UNITY_EDITOR
    var assemblies_usorted = Utils.GetAssemblies();
#else
    assemblies.Add(Assembly.GetExecutingAssembly());
    var assemblies_usorted = AppDomain.CurrentDomain.GetAssemblies();
#endif
    addAssemblieByName(assemblies_usorted, "mscorlib,");
    addAssemblieByName(assemblies_usorted, "System,");
    addAssemblieByName(assemblies_usorted, "System.Core,");
    foreach (Assembly assembly in assemblies_usorted)
    {
        if (!assemblies.Contains(assembly))
        {
            assemblies.Add(assembly);
        }
    }

    this.luaEnv = luaenv;
    // 将lua数据栈上的值转换为c#类型的函数集合
    objectCasters = new ObjectCasters(this);
    // 检查lua数据栈上的值是否是c#类型的函数集合
    objectCheckers = new ObjectCheckers(this);
    // 用于生成和缓存methodWraps
    methodWrapsCache = new MethodWrapsCache(this, objectCheckers, objectCasters);
    // 某些类型通用元表用的方法,例如gc、tostring等。
    metaFunctions = new StaticLuaCallbacks();
    // c#的类型转换为table的方法
    importTypeFunction = new LuaCSFunction(StaticLuaCallbacks.ImportType);
    loadAssemblyFunction = new LuaCSFunction(StaticLuaCallbacks.LoadAssembly);
    castFunction = new LuaCSFunction(StaticLuaCallbacks.Cast);

    // 创建弱value的表,把表缓存在注册表上,返回的cacheRef用于后续获取这个表
    // 当引用类型传入lua时,就会缓存到这个表里。
    LuaAPI.lua_newtable(L);
    LuaAPI.lua_newtable(L);
    LuaAPI.xlua_pushasciistring(L, "__mode");
    LuaAPI.xlua_pushasciistring(L, "v");
    LuaAPI.lua_rawset(L, -3);
    LuaAPI.lua_setmetatable(L, -2);
    cacheRef = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);

    initCSharpCallLua();
}
init_xlua

LuaEnv实例化另一个重点是执行init_xlua的lua代码,这部分lua代码主要行为是:

  • 创建CS表,设置CS的元表,通过元表的__index函数实现首次访问C#类型时才调用对应函数创建该类型的table。
  • 创建全局函数typeof,实际上是返回的是创建C#类型对应table是,设置的UnderlyingSystemType值
  • 创建全局函数cast(xlua.cast)、setenv、getenv、base
  • 新增xlua库函数:hotfix、getmetatable、setmetatable、setclass
具体结合源码上的注释看:
--- LuaEnv构造方法执行的init_lua代码
local metatable = {}
local rawget = rawget
local setmetatable = setmetatable
local import_type = xlua.import_type
local import_generic_type = xlua.import_generic_type
local load_assembly = xlua.load_assembly

function metatable:__index(key)
    local fqn = rawget(self, '.fqn')
    fqn = ((fqn and fqn .. '.') or '') .. key
    -- 是否能导入类型(c#类转为对应table)
    local obj = import_type(fqn)

    if obj == nil then
        -- It might be an assembly, so we load it too.
        obj = { ['.fqn'] = fqn }
        setmetatable(obj, metatable)
    elseif obj == true then
        -- 已导入了类型,直接获取即可
        return rawget(self, key)
    end

    -- Cache this lookup
    rawset(self, key, obj)
    return obj
end

function metatable:__newindex()
    error('No such type: ' .. rawget(self, '.fqn'), 2)
end

-- A non-type has been called; e.g. foo = System.Foo()
function metatable:__call(...)
    local n = select('#', ...)
    local fqn = rawget(self, '.fqn')
    if n > 0 then
        --看是否泛型,如果是,则返回“确定了类型”的泛型
        local gt = import_generic_type(fqn, ...)
        if gt then
            return rawget(CS, gt)
        end
    end
    error('No such type: ' .. fqn, 2)
end

CS = CS or {}
setmetatable(CS, metatable)

typeof = function(t) return t.UnderlyingSystemType end
cast = xlua.cast
if not setfenv or not getfenv then
    local function getfunction(level)
        local info = debug.getinfo(level + 1, 'f')
        return info and info.func
    end

    function setfenv(fn, env)
        if type(fn) == 'number' then fn = getfunction(fn + 1) end
        local i = 1
        while true do
            local name = debug.getupvalue(fn, i)
            if name == '_ENV' then
                debug.upvaluejoin(fn, i, (function()
                    return env
                end), 1)
                break
            elseif not name then
                break
            end

            i = i + 1
        end

        return fn
    end

    function getfenv(fn)
        if type(fn) == 'number' then fn = getfunction(fn + 1) end
        local i = 1
        while true do
            local name, val = debug.getupvalue(fn, i)
            if name == '_ENV' then
                return val
            elseif not name then
                break
            end
            i = i + 1
        end
    end
end

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
xlua.getmetatable = function(cs)
    return xlua.metatable_operation(cs)
end
xlua.setmetatable = function(cs, mt)
    return xlua.metatable_operation(cs, mt)
end
xlua.setclass = function(parent, name, impl)
    impl.UnderlyingSystemType = parent[name].UnderlyingSystemType
    rawset(parent, name, impl)
end

local base_mt = {
    __index = function(t, k)
        local csobj = t['__csobj']
        local func = csobj['<>xLuaBaseProxy_' .. k]
        return function(_, ...)
            return func(csobj, ...)
        end
    end
}
base = function(csobj)
    return setmetatable({ __csobj = csobj }, base_mt)
end以上就是整个LuaEnv初始化的流程。总结下来就是:

  • 初始化lua环境
  • 绑定lua和c#类型转换处理方法
  • 初始化各种基础表。
所以在初始化阶段,是不会把每个c#类型转换为table的,只有访问到才会去创建。下一篇Lua访问c#将会了解到整个创建流程。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-11-26 09:36 , Processed in 0.064270 second(s), 22 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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