当前位置: 首页 > news >正文

XLua 原理分析 三

前面已经介绍了Lua与C#的基础通信原理,和Wrap中间文件的作用。有了前面2篇的基础,大概已经能搞清这块的原理。

为了加深对这块的印象,这里开始正式分析Xlua中的Lua和C#的通信。

一、Lua如何调用CS的过程

lua的初始化代码:

private string init_xlua = @" local metatable = {}local rawget = rawgetlocal setmetatable = setmetatablelocal import_type = xlua.import_typelocal import_generic_type = xlua.import_generic_typelocal load_assembly = xlua.load_assemblyfunction metatable:__index(key) local fqn = rawget(self,'.fqn')fqn = ((fqn and fqn .. '.') or '') .. keylocal 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 thenreturn rawget(self, key)end-- Cache this lookuprawset(self, key, obj)return objendfunction 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 thenlocal gt = import_generic_type(fqn, ...)if gt thenreturn rawget(CS, gt)endenderror('No such type: ' .. fqn, 2)endCS = CS or {}setmetatable(CS, metatable)typeof = function(t) return t.UnderlyingSystemType endcast = xlua.castif not setfenv or not getfenv thenlocal function getfunction(level)local info = debug.getinfo(level + 1, 'f')return info and info.funcendfunction setfenv(fn, env)if type(fn) == 'number' then fn = getfunction(fn + 1) endlocal i = 1while true dolocal name = debug.getupvalue(fn, i)if name == '_ENV' thendebug.upvaluejoin(fn, i, (function()return envend), 1)breakelseif not name thenbreakendi = i + 1endreturn fnendfunction getfenv(fn)if type(fn) == 'number' then fn = getfunction(fn + 1) endlocal i = 1while true dolocal name, val = debug.getupvalue(fn, i)if name == '_ENV' thenreturn valelseif not name thenbreakendi = i + 1endendendxlua.hotfix = function(cs, field, func)if func == nil then func = false endlocal tbl = (type(field) == 'table') and field or {[field] = func}for k, v in pairs(tbl) dolocal cflag = ''if k == '.ctor' thencflag = '_c'k = 'ctor'endlocal f = type(v) == 'function' and v or nilxlua.access(cs, cflag .. '__Hotfix0_'..k, f) -- at least onepcall(function()for i = 1, 99 doxlua.access(cs, cflag .. '__Hotfix'..i..'_'..k, f)endend)endxlua.private_accessible(cs)endxlua.getmetatable = function(cs)return xlua.metatable_operation(cs)endxlua.setmetatable = function(cs, mt)return xlua.metatable_operation(cs, mt)endxlua.setclass = function(parent, name, impl)impl.UnderlyingSystemType = parent[name].UnderlyingSystemTyperawset(parent, name, impl)endlocal base_mt = {__index = function(t, k)local csobj = t['__csobj']local func = csobj['<>xLuaBaseProxy_'..k]return function(_, ...)return func(csobj, ...)endend}base = function(csobj)return setmetatable({__csobj = csobj}, base_mt)end";

这段在该系列的一 里有介绍,就是弄了个CS的table。CS设置了原方法:

__index:查找方法,先看有没有加载过,没有就加载,设置元表是metatable(和CS同一个元表)。然后返回。

import_type:对应C#的ImportType方法。
映射的建立在这里:

压栈import_type字符,压栈C#ImportType方法,压栈raw_set并执行。

C#对应方法:先查有没有映射过,没有就映射然后缓存压栈。

__newindex:调用就报错,可能为了防止查bug不好查吧。

__call:对应C#的构造方法

查找C#的类,优先去调Wrap适配代码,没有就反射查找调用。

Wrap文件加载都会调到__Register进行Lua表的注册,如:

Util的RegisterFunc就是把Lua的元表元素与C#对应的方法进行映射

idx可以简单理解为定义的值

反射部分代码:

在Lua虚拟机创建新的table,然后为其先收集元数据。对应在table里同名进行映射。相比较Wrap模式代码,性能比较浪费。

注册方法为例图示:

最后不论哪种模式,都会走到SetCSTable,期目的是创建一个luatable并把对应C#方法塞进来。

反射的调用走的

把lua和C#靠一个映射关系联系起来,对应key

总结:

1.xlua在程序运行时,先调用init_xlua。目的是创建CS表并且设置__index,__call元方法

2.lua调用C#时,触发元方法,先找缓存没缓存优先加载Wrap文件注册,没有就反射查找。属性什么的都用rawset设置lua与C#方法进行映射

Wrap模式:

反射模式:

3.运行维护堆栈来进行通信(lua找C#对象也就是个Int索引,不会真把内存都压栈给lua)

二、CS如何调用Lua的过程

2.1 C#如何Get Lua代码的数据

ObjectCasters:负责转换lua的数据到C#

看个简单的:

从lua堆栈中取元素,然后转化。取LuaTable也差不多

从下面这段可以看出对应C#而言,lua的table就是一个指针,只需要对应的类来处理这个指针相关的信息就可以达到对lua table操作。

需要注意每次这里都是new个新的table,为了保证唯一性,项目里可以搞个缓存记录一下。避免出现好多个c#的LuaTable指向同一个Lua里的Table。

2.2 C#如何调用lua的方法

这块比较简单,就是简单的压栈操作调用call方法。直接看代码直观

  public object[] Call(object[] args, Type[] returnTypes){
#if THREAD_SAFE || HOTFIX_ENABLElock (luaEnv.luaEnvLock){
#endifint nArgs = 0;var L = luaEnv.L;var translator = luaEnv.translator;int oldTop = LuaAPI.lua_gettop(L);int errFunc = LuaAPI.load_error_func(L, luaEnv.errorFuncRef);LuaAPI.lua_getref(L, luaReference);if (args != null){nArgs = args.Length;for (int i = 0; i < args.Length; i++){translator.PushAny(L, args[i]);}}int error = LuaAPI.lua_pcall(L, nArgs, -1, errFunc);if (error != 0)luaEnv.ThrowExceptionFromError(oldTop);LuaAPI.lua_remove(L, errFunc);if (returnTypes != null)return translator.popValues(L, oldTop, returnTypes);elsereturn translator.popValues(L, oldTop);
#if THREAD_SAFE || HOTFIX_ENABLE}
#endif}
2.3 C#如何设置lua的属性

Set也比较简单,入栈lua部分的key和C#的value直接调用一个 LuaAPI.xlua_psettable。然后再还原堆栈信息

        public void Set<TKey, TValue>(TKey key, TValue value){
#if THREAD_SAFE || HOTFIX_ENABLElock (luaEnv.luaEnvLock){
#endifvar L = luaEnv.L;int oldTop = LuaAPI.lua_gettop(L);var translator = luaEnv.translator;LuaAPI.lua_getref(L, luaReference);translator.PushByType(L, key);translator.PushByType(L, value);if (0 != LuaAPI.xlua_psettable(L, -3)){luaEnv.ThrowExceptionFromError(oldTop);}LuaAPI.lua_settop(L, oldTop);
#if THREAD_SAFE || HOTFIX_ENABLE}
#endif}

测试代码:

        private void Demo5(){LuaEnv luaenv = new LuaEnv();luaenv.DoString(@"luaTable = {a=123,b=456,c=789}function luaTable:Func()print('testLua:'.. tostring(self.a))end go = CS.UnityEngine.GameObject()go.name = 'luaGo'");LuaTable luaTable = luaenv.Global.Get<LuaTable>("luaTable");LuaFunction func = luaTable.Get<LuaFunction>("Func");int a = luaTable.Get<int>("a");func.Call(luaTable);luaTable.Set("a",999);func.Call(luaTable);GameObject go = luaenv.Global.Get<GameObject>("go");go.name = "CSharpGo";luaenv.Dispose();}
Global:对应lua的_G表。

1.先从G表获取名为luaTable的表。调用C#注册通信的lua方法拿到lua的内存指针,并且创建一个C#的LuaTable类。

2.同样的方式拿到Function

3.拿到变量a有所区别,直接从堆栈获取,因为是基础类型

4.调用Call方法,进行压栈。走到  LuaAPI.lua_pcall

5.缓存堆栈信息,进行压栈操作后,设置lua属性。还原堆栈

6.GameObject的生成改名也差不多就不复述了。

更详细的部分得需要去查看lua的c语言实现部分,碍于时间和水平的原因。暂时不详细看了。大体上也是入栈出栈调用pcall方法

相关文章:

  • 返回倒数第 k 个节点 - 力扣(LeetCode)
  • 记录|C# winform布局学习
  • ES中的数据类型学习之ARRAY
  • ChatGPT:宽列数据库是什么?
  • 环境搭建-Docker搭建MySQL
  • webrtc 音频设备操作之opensl与jni
  • [K8S] K8S资源控制器Controller Manager(4)
  • zabbix添加钉钉告警机器人使用bash和python两种脚本
  • 操作系统概念(黑皮书)阅读笔记
  • 集成千兆网口(Gigabit Ethernet Port)的作用主要是提供高速的有线网络连接,其工作原理涉及以下几个关键点:
  • K8S 部署peometheus + grafana 监控
  • 【linux】Shell脚本三剑客之sed命令的详细用法攻略
  • 【MATLAB APP】建立独立桌面APP
  • 相反多位数
  • Python 教程(三):字符串特性大全
  • 〔开发系列〕一次关于小程序开发的深度总结
  • 230. Kth Smallest Element in a BST
  • Codepen 每日精选(2018-3-25)
  • django开发-定时任务的使用
  • ECMAScript入门(七)--Module语法
  • Mac转Windows的拯救指南
  • Python_网络编程
  • Python打包系统简单入门
  • Vue.js-Day01
  • 分布式事物理论与实践
  • 浮动相关
  • 给初学者:JavaScript 中数组操作注意点
  • 力扣(LeetCode)22
  • 力扣(LeetCode)56
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 扑朔迷离的属性和特性【彻底弄清】
  • 前端自动化解决方案
  • 视频flv转mp4最快的几种方法(就是不用格式工厂)
  • 微服务框架lagom
  • ​ubuntu下安装kvm虚拟机
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • #vue3 实现前端下载excel文件模板功能
  • #前后端分离# 头条发布系统
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (1)Nginx简介和安装教程
  • (6)设计一个TimeMap
  • (Forward) Music Player: From UI Proposal to Code
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (苍穹外卖)day03菜品管理
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (四)图像的%2线性拉伸
  • (一)RocketMQ初步认识
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • (转)jdk与jre的区别
  • .cn根服务器被攻击之后
  • .net Application的目录
  • .NET 某和OA办公系统全局绕过漏洞分析