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

相关推荐
okok__TXF2 天前
Nginx + Lua脚本打配合
nginx·lua
miss writer2 天前
Redis分布式锁释放锁是否必须用lua脚本?
redis·分布式·lua
努力--坚持2 天前
电商项目-网站首页高可用(一)
nginx·lua·openresty
黄名富2 天前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
染指11102 天前
50.第二阶段x86游戏实战2-lua获取本地寻路,跨地图寻路和获取当前地图id
c++·windows·lua·游戏安全·反游戏外挂·游戏逆向·luastudio
spencer_tseng3 天前
WeakAuras NES Script(lua)
lua·wow·nes·weakauras
红黑色的圣西罗3 天前
xlua中自定义lua文件加载的一种方式
lua
法外狂徒张三!3 天前
Roblox踩坑1——动画无法完整播放
lua·roblox
冒泡P4 天前
【Lua热更新】上篇
开发语言·数据结构·unity·c#·游戏引擎·lua
两水先木示5 天前
【LuaFramework】LuaFramework_UGUI_V2框架学习
学习·unity·lua·luaframework·tolua