在实际的项目中, 大部分业务逻辑 程序员只需要负责 lua 层编写逻辑即可, 或者在 c# 层添加一些静态函数, 供 lua 层调用. 那么对于具体的相互之间的交互, 又是如何进行的? 本文就写一写个人的一些探究笔记吧. 本文会写很多代码, 我就用截图来展示吧, 编辑写代码不大方便, 有点蛋疼~
一, c# 函数的导出
我就从外部接口开始理一遍整体思路吧, 想了一下, 还是从代码思路来解释比较容易.
首先我们的工程中都会有一个 slua 的导出接口:
这样的一个接口, 是用来将 UnityEngine 中的类导出的实现 API, 其整体的思路是:
1) 首先加载 UnityEngine 这个程序集:
Assembly assembly = Assembly.Load("UnityEngine")
2) 然后获取资中的可导出类型:
Type[] types = assembly.GetExportedTypes();
3) 做一次过滤, 主要是对于某些需要导出的类和不需要导出的类做一次过滤剔除和添加, 这个不同项目不一样, 不做展示;
4) 将这些过滤后的类型, 逐个做一次导出, 比如相机类, 可以导出为:
5) 将这些导出的类 Lua_xxx 合并在一起作为一个 Bind, 提供一个静态获取方法 GetBindList()
这是第一步, 完成对 c# 和 unity 中的方法导出, 将每个不同程序集中的类中的方法和属性都暴露出来, 做一个导出.
二, 导出的 c# 文件的注册到 Lua 虚拟机中
这部分需要结合游戏的启动来理解, 在游戏的启动时刻, 我们都会启动一个 Lua 的虚拟机, 比如这样:
在启动虚拟机后, 需要执行虚拟机的 Init 操作:
m_LuaSvr.init(xxxx)
在这个函数中, 执行 Bind 的操作: doBind
其中的关键操作为 collectBindInfo, 这个函数分为 2 部分:
1) 获取当前程序集, 以及程序集中设置为 LuaBinderAttribute 的类型:
2) 根据获取的类型, 逐个反射执行第一部分最后的 GetBindList 函数:
这样通过 c# 的反射, 就可以动态的获取前面导出的所有 LuaXXX 类文件了, 回到 Bind 操作, 对于这些获取的 Lua_XXX 文件, 执行 Lua 虚拟机的注册操作:
action(L)
也就是导出文件中的 reg 操作:
看看其操作, 首先是 newtable 的操作:
创建 2 个 table, 分别用来做 static 和 instance 的填充, 然后填充的操作 addMember:
对于不同的参数, 会重载不同的 addMember 操作, 这儿就举例一个, pushValue 就是将 func 注册到该 table 中:
LuaDll.lua_pushcclosure(L, function, 0)
就是将该函数填充到 lua 表中, 可以通过 key 名的查找来获取该函数, 从而执行相关的调用.
最后会在该 reg 操作中为该类创建一个 metatable
回到最初的, 不断的循环执行, 就可以加载整个 c# 相关导出类到 Lua 虚拟机中
总结: 到现在为止, 可以知道整个 c# 函数在导出过程中的操作, 在启动时候如何通过程序集和反射来实现动态的加载, 最后 Lua 的虚拟机中都会注册前面导出的类文件的相关函数和属性.
而我们已经知道, lua 文件在执行的时候, 是会编译成字节码在 lua 的虚拟机中执行的, 这样 lua 的字节码和 c# 的导出文件, 都在同一个环境中执行, 调用 pcall 就可以相互的执行和调用了.
写这篇文章是基于偶然翻看到老外写的一个在 unity 中用 c++ 做脚本来编写游戏逻辑, 并且实现了 c# 和 c++ 之间的相互交互调用, 所以我也翻看了一下 c# 是如何实现的, 当然写的比较简陋, 还有很多细节需要推敲, 大家可以翻看自己的项目代码, 留言讨论
来源: https://www.cnblogs.com/zblade/p/8927127.html