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

XLua 源码学习原理(一)

XLua 源码学习原理(一)

https://blog.csdn.net/zhaoguanghui2012/article/details/101675435

@https://zhuanlan.zhihu.com/p/68406928

 

刚刚初学XLua的源码,作为一个记录。

  • XLua调用C#代码

在XLua中可以直接使用Lua代码来调用C#中的代码。

CS.UnityEngine.Debug.Log('hello world')

接下来就是记录XLua是如何实现其调用的.

  • XLua中的CS变量的功能

CS是一个全局的Table,所以CS.UnityEngine可以当做是在一个名为CS的Table表中查询名为UnityEngine的值。获取其值是通过CS的元方法__index来实现的。其逻辑代码在创建LuaEnv时候调用下面的代码,进行CS表的初始化。

DoString(init_xlua, "Init");

下面代码是截取了部分的init_xlua代码。

这部分描述的是_index元方法的实现元方法__index就是CS表中访问不存在的元素时候进行的操作。比如CS={'A='a','B'='b'},那么在Lua中直接访问CS.A就会返回a。但是如果访问C就会因为原来表中不存在这个记录,那么而调用__index这个方法。

代码实现注释如下

 
  1. inti_xlua.lua

  2. local metatable = {}

  3. local rawget = rawget

  4. local setmetatable = setmetatable

  5. local import_type = xlua.import_type

  6. local import_generic_type = xlua.import_generic_type

  7. local load_assembly = xlua.load_assembly

  8. function metatable:__index(key)

  9. --查询自己Key为'.fqn'的值,并且不触发__index元方法

  10. local fqn = rawget(self,'.fqn')

  11. --拼接'.fqn'的值和本次调用的key

  12. fqn = ((fqn and fqn .. '.') or '') .. key

  13. --尝试查询CS类型.

  14. local obj = import_type(fqn)

  15. if obj == nil then

  16. -- It might be an assembly, so we load it too

  17. --如果为空,有可能这个字段还是类名的一部分,那么创建一个table记录,然后缓存返回.

  18. obj = { ['.fqn'] = fqn }

  19. setmetatable(obj, metatable)

  20. elseif obj == true then

  21. return rawget(self, key)

  22. end

  23. -- Cache this lookup

  24. rawset(self, key, obj)

  25. return obj

  26. end

  27. CS = CS or {}

  28. setmetatable(CS, metatable)

  • Lua中获取C#类对应的Table表
XLua中有两种方式来实现Lua调用CS中的方法,一种是反射来调用,一种是生成适配的代码。

在获取对应类的Lua表时候,使用的是import_type方法,也是在创建LuaEnv实例时候进行注册的代码如下。

 
  1. ObjectTranslator.cs

  2. public void OpenLib(RealStatePtr L) {

  3. if (0 != LuaAPI.xlua_getglobal(L, "xlua")){ throw new Exception("call xlua_getglobal fail!" + LuaAPI.lua_tostring(L, -1));}

  4. LuaAPI.xlua_pushasciistring(L, "import_type");

  5. LuaAPI.lua_pushstdcallcfunction(L,importTypeFunction);

  6. LuaAPI.lua_rawset(L, -3);

  7. ...

  8. }

上面代码中的importTypeFunction是一个C#委托当Lua中是调用import_type时候Lua会调用对应的C方法(Lua调用CFunction的原理,请查找Lua手册),最后会调用到对应的C#委托上来。

其中xlua全局table是在C中设置的代码如下:

 
  1. XLua.c

  2. LUA_API void luaopen_xlua(lua_State *L) {

  3. luaL_openlibs(L);

  4. #if LUA_VERSION_NUM == 503

  5. luaL_newlib(L, xlualib);

  6. lua_setglobal(L, "xlua");

  7. #else

  8. luaL_register(L, "xlua", xlualib);

  9. lua_pop(L, 1);

  10. #endif

  11. }

代码很简单,luaopen_xlua是一个c函数,属于xlua.dll在创建LuaEnv时候会调用。调用后会设置一个全局变量xlua,也就是ObjectTranslator类中获取的xlua变量。然后将键值对"import_type"=C#委托,压入xlua表中。这样就能在inti_xlua.lua中调用import_type方法了。

  • C#中查找指定Type对应的Lua表的实现

在C#的ImportType方法中会尝试在缓存中获取对应的Type。如果Type为空,那么说明是第一次尝试引用对应的Type,代码就会判断是时使用生成适配代码还是反射模式,来生成对应的表。

代码如下

 
  1. 省略部分代码

  2. ObjectTranslator.cs

  3. public static int ImportType(RealStatePtr L)

  4. {

  5. try

  6. {

  7. ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);

  8. //需要查询的类名

  9. string className = LuaAPI.lua_tostring(L, 1);

  10. //查找C#对应的Type(此处还没去查找对应Lua的表)

  11. Type type = translator.FindType(className);

  12. if (type != null)

  13. {

  14. //这句查找Lua中Type对应的表

  15. if (translator.GetTypeId(L, type) >= 0)

  16. {

  17. LuaAPI.lua_pushboolean(L, true);

  18. }

  19. else

  20. {

  21. return LuaAPI.luaL_error(L, "can not load type " + type);

  22. }

  23. }

  24. else

  25. {

  26. LuaAPI.lua_pushnil(L);

  27. }

  28. return 1;

  29. }

  30. }

  31.  
  32. internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN)

  33. {

  34. int type_id;

  35. is_first = false;

  36. //查询是否缓存中有Type对应的Lua表,有就直接返回

  37. if (!typeIdMap.TryGetValue(type, out type_id)) // no reference

  38. {

  39. ...

  40. is_first = true;

  41. Type alias_type = null;

  42. aliasCfg.TryGetValue(type, out alias_type);

  43. LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);

  44. if (LuaAPI.lua_isnil(L, -1)) //no meta yet, try to use reflection meta

  45. {

  46. LuaAPI.lua_pop(L, 1);

  47. //此处会去检查是使用反射还是生成适配代码的逻辑

  48. if (TryDelayWrapLoader(L, alias_type == null ? type : alias_type))

  49. {

  50. LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);

  51. }

  52. else

  53. {

  54. throw new Exception("Fatal: can not load metatable of type:" + type);

  55. }

  56. }

  57. //循环依赖,自身依赖自己的class,比如有个自身类型的静态readonly对象。

  58. if (typeIdMap.TryGetValue(type, out type_id))

  59. {

  60. LuaAPI.lua_pop(L, 1);

  61. }

  62. else

  63. {

  64. ...

  65. LuaAPI.lua_pushvalue(L, -1);

  66. type_id = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);

  67. LuaAPI.lua_pushnumber(L, type_id);

  68. LuaAPI.xlua_rawseti(L, -2, 1);

  69. LuaAPI.lua_pop(L, 1);

  70. ...

  71. //缓存type与其对应到lua中的表

  72. typeIdMap.Add(type, type_id);

  73. }

  74. }

  75. return type_id;

  76. }

  77.  
  78. public bool TryDelayWrapLoader(RealStatePtr L, Type type)

  79. {

  80. if (loaded_types.ContainsKey(type)) return true;

  81. loaded_types.Add(type, true);

  82. LuaAPI.luaL_newmetatable(L, type.FullName); //先建一个metatable,因为加载过程可能会需要用到

  83. LuaAPI.lua_pop(L, 1);

  84. Action<RealStatePtr> loader;

  85. int top = LuaAPI.lua_gettop(L);

  86. //此处如果已经缓存,那么就是生成适配代码注册,

  87. //这边的逻辑也是为了用的时候才实例化对应的.

  88. //这个delayWrap是个字典,他的键值对在XLua_Gen_Initer_Register__类实例化时候自动填充

  89. if (delayWrap.TryGetValue(type, out loader))

  90. {

  91. delayWrap.Remove(type);

  92. //将类方法,字段,成员等加载上来

  93. loader(L);

  94. }

  95. //那么这里就是反射的逻辑了

  96. else

  97. {

  98. ...

  99. //用反射将类方法,字段,成员等加载上来

  100. Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));

  101. ...

  102. }

  103. ...

  104. ...

  105. return true;

  106. }

在生成完Type对应的Lua表后还需要设置到Lua上去

下面的代码简单来说就是用前面代码生成的table表设置到CS.UnityEngine[Debug]中

 
  1. //loader(L)和Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));中都会调用此函数来设置CS.UnityEngine[Debug]

  2. public static void SetCSTable(RealStatePtr L, Type type, int cls_table)

  3. {

  4. int oldTop = LuaAPI.lua_gettop(L);

  5. cls_table = abs_idx(oldTop, cls_table);

  6. LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);

  7. LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);

  8. List<string> path = getPathOfType(type);

  9. for (int i = 0; i < path.Count - 1; ++i)

  10. {

  11. LuaAPI.xlua_pushasciistring(L, path[i]);

  12. if (0 != LuaAPI.xlua_pgettable(L, -2))

  13. {

  14. LuaAPI.lua_settop(L, oldTop);

  15. throw new Exception("SetCSTable for [" + type + "] error: " + LuaAPI.lua_tostring(L, -1));

  16. }

  17. if (LuaAPI.lua_isnil(L, -1))

  18. {

  19. LuaAPI.lua_pop(L, 1);

  20. LuaAPI.lua_createtable(L, 0, 0);

  21. LuaAPI.xlua_pushasciistring(L, path[i]);

  22. LuaAPI.lua_pushvalue(L, -2);

  23. LuaAPI.lua_rawset(L, -4);

  24. }

  25. else if (!LuaAPI.lua_istable(L, -1))

  26. {

  27. LuaAPI.lua_settop(L, oldTop);

  28. throw new Exception("SetCSTable for [" + type + "] error: ancestors is not a table!");

  29. }

  30. LuaAPI.lua_remove(L, -2);

  31. }

  32. LuaAPI.xlua_pushasciistring(L, path[path.Count - 1]);

  33. LuaAPI.lua_pushvalue(L, cls_table);

  34. LuaAPI.lua_rawset(L, -3);

  35. LuaAPI.lua_pop(L, 1);

  36. LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);

  37. LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);

  38. ObjectTranslatorPool.Instance.Find(L).PushAny(L, type);

  39. LuaAPI.lua_pushvalue(L, cls_table);

  40. LuaAPI.lua_rawset(L, -3);

  41. LuaAPI.lua_pop(L, 1);

  42. }

现在可以看到在调用到CS.UnityEngine.Debug时候,我们在Lua中已经获取到了这个类对应Lua Table了,那么接下来调用CS.UnityEngine.Debug.Log("hello world"),大提上与之前的获取Type类是一致的。不过要注意的是调用static的方法字段和对象的方法字段使用的是不同的table。这次文章都讨论的是静态方法的调用.

  • 调用指定的C#方法

上面的已经提到XLua中有两种方式来实现Lua调用CS中的方法,一种是反射来调用,一种是生成适配的代码.

使用生成适配代码调用

在XLua中生成适配代码后会在Gen目录生成代码如下

 
  1. UnityEngineDebugWrap.cs

  2. public class UnityEngineDebugWrap

  3. {

  4. public static void __Register(RealStatePtr L)

  5. {

  6. ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);

  7. System.Type type = typeof(UnityEngine.Debug);

  8. //注册成员方法等

  9. Utils.BeginObjectRegister(type, L, translator, 0, 0, 0, 0);

  10. Utils.EndObjectRegister(type, L, translator, null, null,null, null, null);

  11. //注册类方法等即Static

  12. Utils.BeginClassRegister(type, L, __CreateInstance, 17, 3, 1);

  13. ...

  14. //注册一个名为Log的回调

  15. Utils.CLS_IDX(L, Utils.CLS_IDX, "Log", _m_Log_xlua_st_);

  16. ...

  17. Utils.EndClassRegister(type, L, translator);

  18. }

  19.  
  20.  
  21.  
  22. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]

  23. static int _m_Log_xlua_st_(RealStatePtr L)

  24. {

  25. //根据Log方法的参数数量来生成各种调用

  26. try {

  27. ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);

  28. int gen_param_count = LuaAPI.lua_gettop(L);

  29. if(gen_param_count == 1&& translator.Assignable<object>(L, 1))

  30. {

  31. object _message = translator.GetObject(L, 1, typeof(object));

  32. UnityEngine.Debug.Log( _message );

  33. return 0;

  34. }

  35. if(gen_param_count == 2&& translator.Assignable<object>(L, 1)&& translator.Assignable<UnityEngine.Object>(L, 2))

  36. {

  37. object _message = translator.GetObject(L, 1, typeof(object));

  38. UnityEngine.Object _context = (UnityEngine.Object)translator.GetObject(L, 2, typeof(UnityEngine.Object));

  39. UnityEngine.Debug.Log( _message, _context );

  40. return 0;

  41. }

  42.  
  43. } catch(System.Exception gen_e) {

  44. return LuaAPI.luaL_error(L, "c# exception:" + gen_e);

  45. }

  46. return LuaAPI.luaL_error(L, "invalid arguments to UnityEngine.Debug.Log!");

  47. }

  48. }

  49. 注册代码如下

  50. Utils.cs

  51. public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func)

  52. {

  53. //这里的idx指的是就是CLS_IDX,就是cls_table,也就是SetCSTable设置的表

  54. idx = abs_idx(LuaAPI.lua_gettop(L), idx);

  55. //压入方法名

  56. LuaAPI.xlua_pushasciistring(L, name);

  57. //压入C#委托指针

  58. LuaAPI.lua_pushstdcallcfunction(L, func);

  59. LuaAPI.lua_rawset(L, idx);

  60. }

使用反射式调用

 
  1. static int FixCSFunction(RealStatePtr L)

  2. {

  3. try

  4. {

  5. ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);

  6. //这边获取闭包中的upvalue值

  7. int idx = LuaAPI.xlua_tointeger(L, LuaAPI.xlua_upvalueindex(1));

  8. //GetFixCSFunction很简单就是return fix_cs_functions[index];

  9. //fix_cs_functions这个是在PushFixCSFunction时候添加的,PushFixCSFunction是在之前ReflectionWrap中调用的

  10. LuaCSFunction func = (LuaCSFunction)translator.GetFixCSFunction(idx);

  11. return func(L);

  12. }

  13. catch (Exception e)

  14. {

  15. return LuaAPI.luaL_error(L, "c# exception in FixCSFunction:" + e);

  16. }

  17. }

那么到现在为止所有代码设置都已经完成,就差调用了。

当DoString到CS.UnityEngine.Debug.Log("hello world")时候,先从CS.UnityEngine.Debug[Log]获取到对应的value,在lua中这个值是一个function,那么就执行call,压入参数然后就开始调用了。如果是生成是适配代码的方式的话其对应的C#委托就是 _m_Log_xlua_st_(RealStatePtr L)了。但是如果是反射式调用的话,其对应的C#委托永远都是StaticLuaCallbacks.FixCSFunctionWraper这个委托,就是上面代码的FixCSFunction。当调用FixCSFunction后会从中取出upvalue,这个值是一个数字,是个索引。索引的是之前生成wrap时候缓存的方法。然后直接进行调用。

到此整一个调用就结束了.

  • 点赞
  • 评论
  • 分享
  • 收藏 5
  • 举报
  • 关注
  • 一键三连

    点赞Mark关注该博主, 随时了解TA的最新博文

Unity中SLua、Tolua、XLua和ILRuntime效率评测

u011467512的博客

3万+

Unity Lua 效率对比

lua 热更新原理(一)

专注于网络编程,游戏后台,高并发

2万+

热更一般是针对模块的线上替换。我们动态加载要热更的模块试试。例如,我们在循环间隔加载某模块,这样就能有时间去更改模块了。测试的代码如下: for i = 1, 5 do local m = require('other'

 

 

 

 

 

 

 

 

 

 

 

 

相关文章:

  • [PyQt] 使用.qrc 生成资源文件供程序中使用
  • [Qt]设置窗口图标和EXE应用程序图标
  • 蓝噪声取样(Blue noise sampling) 相关知识
  • 关于cmd运行自动进行远程连接(自动填写用户及密码)
  • mstsc保存用户名和密码,实现自动登录远程桌面
  • mstsc命令详解
  • Loading.UpdatePreloading是什么东西,为什么会突然那么高?
  • unity中Loding.UpdatePreloading占用CPU过高如何解决?
  • [总结] 漫谈HDR和色彩管理(四)HDR标准和ACES
  • 视频名词浅析——HDR
  • 虚幻引擎学习之路:渲染模块之全局光照明
  • 我所理解的DirectX Ray Tracing
  • Unity 曲线插值(Hermite插值和Catmull_Rom插值)
  • 参数化曲线:Hermite Catmull-Rom Bezier
  • 贝塞尔曲线原理(简单阐述)
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • ES6语法详解(一)
  • FastReport在线报表设计器工作原理
  • Hibernate最全面试题
  • JavaScript函数式编程(一)
  • Kibana配置logstash,报表一体化
  • React+TypeScript入门
  • REST架构的思考
  • vue自定义指令实现v-tap插件
  • 分享几个不错的工具
  • 开源地图数据可视化库——mapnik
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 深入浏览器事件循环的本质
  • # Maven错误Error executing Maven
  • #{}和${}的区别是什么 -- java面试
  • $L^p$ 调和函数恒为零
  • (33)STM32——485实验笔记
  • (分类)KNN算法- 参数调优
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (小白学Java)Java简介和基本配置
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • ***原理与防范
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .gitignore文件设置了忽略但不生效
  • .Net Core与存储过程(一)
  • .net 程序 换成 java,NET程序员如何转行为J2EE之java基础上(9)
  • .NET 线程 Thread 进程 Process、线程池 pool、Invoke、begininvoke、异步回调
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .NET命名规范和开发约定
  • .sdf和.msp文件读取
  • /proc/vmstat 详解
  • @Transactional 竟也能解决分布式事务?
  • [.net] 如何在mail的加入正文显示图片
  • [AI]文心一言出圈的同时,NLP处理下的ChatGPT-4.5最新资讯
  • [Asp.net MVC]Asp.net MVC5系列——Razor语法
  • [Asp.net MVC]Bundle合并,压缩js、css文件
  • [AutoSar]工程中的cpuload陷阱(三)测试
  • [C#]winform制作仪表盘好用的表盘控件和使用方法
  • [Django 0-1] Core.Checks 模块