XLua原理(一)

项目中活动都是用xlua开发的,项目周更热修也是用xlua的hotfix特性来做的。现研究底层原理,对于项目性能有个更好的把控。

本文认为看到该文章的人已具备使用xlua开发的能力,只研究介绍下xlua的底层实现原理。

一.lua和c#交互原理

概括:

通过栈来实现。lua调用c#就是将lua层的参数和c#导出函数入栈,然后执行函数。c#调用lua就是将c#层的参数和lua函数入栈,然后执行函数

1.1 C#访问lua

直接上代码:

cs 复制代码
 private void Demo1()
        {
            IntPtr L = LuaAPI.luaL_newstate();
            if (LuaAPI.luaL_loadbuffer(L, @"function addandsub(x,y) 
            return x+y , x-y end", "selfTagChunk") != 0)
            {
                Debug.LogError(LuaAPI.lua_tostring(L,-1));
            }

            //LuaAPI.lua_pushnumber(L,20);
            //LuaAPI.lua_pushnumber(L,21);
            LuaAPI.lua_pcall(L, 0, 0, 0);
            LuaAPI.xlua_getglobal(L, "addandsub");
            LuaAPI.lua_pushnumber(L,10);
            LuaAPI.lua_pushnumber(L,7);
          
            int valueB = LuaAPI.xlua_tointeger(L, -1);//7
            int valueB2 = LuaAPI.xlua_tointeger(L, -2);//10
            LuaTypes luaTypeB3 = LuaAPI.lua_type(L, -3);//function
            
            int valueC4 = LuaAPI.xlua_tointeger(L, 3);//7
            int valueC3 = LuaAPI.xlua_tointeger(L, 2);//10
            LuaTypes luaTypeC2 = LuaAPI.lua_type(L, 1);//function
            
            if (LuaAPI.lua_pcall(L, 2, 2, 0) != 0)
            {
                Debug.LogError(LuaAPI.lua_tostring(L,-1));
            }
            
            int value = LuaAPI.xlua_tointeger(L, -1); //3
            int value2 = LuaAPI.xlua_tointeger(L, -2); //17
            LuaAPI.lua_close(L);
        }

Api备注:

LuaAPI.luaL_newstate:开辟lua虚拟机,执行lua程序

LuaAPI.lua_close:关闭lua虚拟机,释放资源

LuaAPI.luaL_loadbuffer:编译一段lua代码,但没执行

LuaAPI.lua_pcall:执行lua代码,这时候可以用

这里:先把addandsub方法压栈,然后把10和7分别压栈。所以看到的内存里的数据如批注所示。

在执行第2个lua_pcall。会把之前的堆栈信息清空,把函数执行返回的结果进行压栈操作

1.2 lua访问C#

cs 复制代码
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void TestCSFunction(IntPtr L);

        [MonoPInvokeCallback(typeof(TestCSFunction))]
        public static void TestLuaCallCSharp(IntPtr L)
        {
            Debug.Log("TestLuaCallCSharp");
        }
        
        private void Demo2()
        {
            IntPtr L = LuaAPI.luaL_newstate();
            //Marshal 提供对非托管类型的操作
            IntPtr function = Marshal.GetFunctionPointerForDelegate(new TestCSFunction(TestLuaCallCSharp));
            //函数入栈
            LuaAPI.lua_pushcclosure(L,function,0);
            LuaAPI.lua_pcall(L, 0, 0, 0);
            LuaAPI.lua_close(L);
        }

UnmanagedFunctionPointer:定义为了让其不受C#托管管理

MonoPInvokeCallback:标记可以使其用C或C++调用

上述可以看到是把C#函数包装成指针,然后进行压栈操作供lua调用

二.xlua中的LuaEnv

cs 复制代码
  private void Demo3()
  {
     LuaEnv luaenv = new LuaEnv();
     luaenv.DoString("CS.UnityEngine.Debug.Log('hello world')");
     luaenv.Dispose();
  }

LuaEnv:是xlua封装好的lua环境,类似上面的luaL_newstate。

具体的事情:

XLua框架中最重要的一个类,那就是LuaEnv。它包含了lua中的状态机RealStatePrt。lua的G表,还有注册表LuaIndexes.LUA_REGISTRYINDEX等等,下面从LuaEnv的构造函数开始,看看这个类做了些什么事情

cs 复制代码
//节选LuaEnv构造函数部分代码

//拿到Lua中的注册表
LuaIndexes.LUA_REGISTRYINDEX = LuaAPI.xlua_get_registry_index

//创建Lua状态机
rawL = LuaAPI.luaL_newstate()

//十分重要的一个对象,用于c#和lua的交互
translator = new ObjectTranslator(this, rawL); 
translator.createFunctionMetatable(rawL);	//添加_gc元方法到注册表
translator.OpenLib(rawL);	//将init_xlua中会用到的方法,全部定义出来

//添加搜索路径
AddSearcher(StaticLuaCallbacks.LoadBuiltinLib, 2); 
//添加自定义解析Lua文件的方法,对应的是LuaEnv.CustomLoader
AddSearcher(StaticLuaCallbacks.LoadFromCustomLoaders, 3);
#if !XLUA_GENERAL
AddSearcher(StaticLuaCallbacks.LoadFromResource, 4);
AddSearcher(StaticLuaCallbacks.LoadFromStreamingAssetsPath, -1);
#endif

//十分重要!! 初始化xLua
DoString(init_xlua, "Init");

#if !UNITY_SWITCH || UNITY_EDITOR
AddBuildin("socket.core", StaticLuaCallbacks.LoadSocketCore);
AddBuildin("socket", StaticLuaCallbacks.LoadSocketCore);
#endif
AddBuildin("CS", StaticLuaCallbacks.LoadCS);

调用Init方法:

cs 复制代码
private string init_xlua = @" 
            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

            --fqn就是类型和命名空间名,通过import_type去获取对应的udata并且入栈
            function metatable:__index(key) 
                --查询key不调用元方法,更简单的表达只在自己的表内查询
                local fqn = rawget(self,'.fqn')
                fqn = ((fqn and fqn .. '.') or '') .. key
                --查询C#类型
                local obj = import_type(fqn)
                --如果不是 再次查询C#命名空间
                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
            --既然是C#对象 就不要再newindex了,避免产生未知的错误
            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 这下知道了 typeof是xlua自己搞的,不是lua本身语言特性
            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
            ";

设置了metatble扩充原方法,并且设置CS的元表示metatable:

__index:会把C#没有缓存的命名空间下的元数据导入进来。

__call:与index同理,这里能看出来了为什么在lua里调C#方法是CS.UnityEngine.Vector3(x,x,x)。

这步是让lua可以去访问C#对象。

因为在Lua中定义了全局的CS表,并且在lua中调用CS的时候,会先调用StaticLuaCallbacks.LoadCS获取到注册表中的CS表,然后在调用XXX的时候,如果访问到了CS表中不存在的元素,则会调用其元表,在元表中通过映射到c#的StaticLuaCallbacks.ImportType方法完成查找。

lua在查找时调用了import_type进行查找调用,提前在OpenLib里把方法进行注册:

而importType会从中转器查询,查询的过程就是从一个维护的字典里看能不能取到,取不到就加载命名空间并注入到lua虚拟机中。

后面介绍lua与C#之间通信会详解介绍

参考:

Lua与C#交互原理(转)_lua与c#的交互原理-CSDN博客

https://zhuanlan.zhihu.com/p/441169478

相关推荐
UWA4 天前
Unreal开发痛点破解!GOT Online新功能:Lua全监控 + LLM内存可视化!
开发语言·lua·unreal
1nullptr4 天前
Lua迭代器与泛型for
lua
半夏知半秋4 天前
skynet debug_console控制台中debug指令使用
服务器·开发语言·学习·lua
h7997104 天前
redis lua脚本(go)调用教程以及debug调试
redis·golang·lua
玩转C语言和数据结构7 天前
Lua下载和安装教程(附安装包)
lua·lua下载·lua安装教程·lua下载和安装教程·lua安装包
Arva .7 天前
HTTP Client
网络协议·http·lua
爱吃小胖橘8 天前
Lua语法(2)
开发语言·unity·lua
ellis19709 天前
LuaC API知识点汇总
unity·lua
爱吃小胖橘12 天前
Lua语法
开发语言·unity·lua
东方芷兰12 天前
JavaWeb 课堂笔记 —— 20 SpringBootWeb案例 配置文件
java·开发语言·笔记·算法·log4j·intellij-idea·lua