LuaInterface是一个实现lua和微软.Net平台的CLR混合编程的开源库。使得lua脚本可以实例化CLR对象、访问属性、调用方法、甚至使用lua函数来处理事件。
tolua基于LuaInterface。tolua在LuaInterface基本形式的基础上,使代码更简洁,提供了对Unity的支持。两者最大区别在于:LuaInterface中lua访问CLR需要运行时反射,tolua则是通过预先生成wrap进行访问。
1.Unity的tolua插件使用
将类名添加到CustomSetting.cs中,点击菜单栏的Clear wrap files,然后点击Generate All。
会产生如下变化
自动生成一个Wrap文件
会在LuaBinder.cs文件中添加Warp文件的注册
1.1lua中调用C#方法
实际上是把参数压栈,调用了Wrap文件中的静态方法,然后在该方法中再执行C#的部分。
--调用类的静态方法
local go = UnityEngine.GameObject.Find("LuaScene")
--调用对象的成员方法,注意是用冒号调用,相当于把自身作为第一个参数压栈了。
local access = go:GetComponent("CSharpAccess")
1.2C#中调用lua方法
通过luaState执行脚本或者获得函数。
void Start()
{
luaState = new LuaState(); // 启动1:创建tolua提供的LuaState对象。
luaState.Start(); // 启动2:虚拟机初始化。
LuaBinder.Bind(luaState); // 启动3:Lua-c#中间层wrap文件们分别向虚拟机注册自己。
//执行lua脚本或者lua函数
luaState.DoFile("Main.lua");
luaState.Require("Main.lua");
LuaFunction main = luaState.GetFunction("Main");
main.Call();
main.Dispose();
main = null;
}
2.tolua机制
2.1 C#调用原生代码
DllImport函数,功能是提供从非托管DLL导出的函数的必要调用信息。toluaUnity项目中提供了一个LuaDLL.cs,在C#中又对tolua.dll进行了一次封装。
2.2 数据准备
tolua启动的时候会注册Wrap文件,在luaState中创建好了模块和类,这些函数的背后执行了原生代码。
//LuaBinder.cs
L.BeginModule("UnityEngine");
L.BeginClass(typeof(UnityEngine.Component), typeof(UnityEngine.Object));
L.RegFunction("GetComponent", GetComponent);
L.RegVar("transform", get_transform, null);
L.RegVar("gameObject", get_gameObject, null);
L.RegVar("tag", get_tag, set_tag);
L.EndClass();
L.EndModule();
BeginModule(null)代表当前命名空间是global全局域,而BeginModule(“UnityEngine”)则代表后面都注册到_G[“UnityEngine”]表中。
BeginClass相当于是类的注册。
创建了一个叫Component的table,并复制一份压入栈中,有两个table;
栈顶table设置userdata、.name、__index、__newindex等;
弹出栈顶table,并将其设置为第二个table的元表;
RegFunction注册函数,将注册的方法转换成供平台使用的之神,传递到C中生成可以供lua使用的LuaCSFunction函数,最终生成闭包,存到当前栈顶table中。
2.3 lua调用C#的对象和方法
2.3.1对象
实例存储在C#中LuaState的ObjectTranslator中,lua中的变量只是一个持有该C#实例索引位置的fulluserdata,并没有直接对C#实例进行引用。lua调用时会把fulluserdata压栈,C#层取到索引后,可以通过ObjectTranslator.GetObject(intudata)获得对象。
2.3.2方法
静态方法调用
直接从类的表里搜索函数。
成员方法调用
在lua中对象实际上是userdata,类的table是它的元表,所以从类的table中搜索函数。因为是冒号调用的,所以把自己作为第一个参数和其他参数一起压栈。
调用Wrap中的函数,根据参数个数进行重载。从栈中获取参数,调用的对象可以通过userdata的索引从ObjectTranslator中获取,并执行C#中的代码。
2.4 对Vector3的优化
原因是boxing(装箱)和unboxing(拆箱)。Vector3(栈)转为object类型需要boxing(堆内存中),object转回Vector3需要unboxing,使用后释放该object引用,这个堆内存被gc检测到已经没引用,释放该堆内存,产生一个gc内存。
关于结构体,目前只支持一些特定的结构体,需要在lua中对应一份实现(Assets\ToLua\Lua目录中)。例如:Vector3对应的实现目录是Assets\ToLua\Lua\UnityEngine\Vector3.lua。所以说tolua用lua重新实现了Vector3。
2.4.1 tolua创建Vector3
Vector3.New(x, y, z)
toLua并没有跟UnityC#交互。
2.4.2 C#传Vector3到lua
C# UnityEngine_TransformWrap.get_position;调用ToLua.Push
把Vector的x,y,z三个值拆开传过去;
在lua创建一个lua table,把x,y,z设置为对应字段。
2.4.3 lua传Vector3到C#
从栈中去除对应table的x,y,z;
C# new一个Vector3,将x,y,z赋值到Vector3。
3.参考资料
【ToLua】源码学习总结 https://zhuanlan.zhihu.com/p/320575488
tolua之wrap文件的原理与使用 https://www.cnblogs.com/blueberryzzz/p/9672342.html
ToLua:逐行分析源码,搞清楚Wrap文件原理 https://blog.csdn.net/qq_28820675/article/details/106864636
ToLua源码分析:启动流程 https://blog.csdn.net/lodypig/article/details/60160020
tolua#代码简要分析 https://www.cnblogs.com/ghl_carmack/p/6720500.html
Unity中xLua与toLua对Vector3的优化 https://www.jqhtml.com/53934.html