tolua源码之小白剖析(二)
LuaState
上一篇文章,我们分析了C层Api的架构流程和部分C#层Api的架构流程,并最终在Unity端成功运行起了lua代码。这一篇文章,我们就来分析一下C#层最核心的LuaState它做了什么。
首先以官方Demo开头:
using UnityEngine;
using LuaInterface;
using System;
public class HelloWorld : MonoBehaviour
{
void Awake()
{
LuaState lua = new LuaState();
lua.Start();
string hello =
@"
print('hello tolua#')
";
lua.DoString(hello, "HelloWorld.cs");
lua.CheckTop();
lua.Dispose();
lua = null;
}
}
可以注意到,在执行lua代码之前,必须首先实例化LuaState,通过它去执行lua代码,这个LuaState就是Lua虚拟机,那我们先看看它的初始化做了些什么工作吧。首先贴一下LuaState的构造函数,对它有个整体认知:
public LuaState()
{
if (mainState == null)
{
mainState = this;
// MULTI_STATE Not Support
injectionState = mainState;
}
float time = Time.realtimeSinceStartup;
InitTypeTraits();
InitStackTraits();
L = LuaNewState();
LuaException.Init(L);
stateMap.Add(L, this);
OpenToLuaLibs();
ToLua.OpenLibs(L);
OpenBaseLibs();
LuaSetTop(0);
InitLuaPath();
Debugger.Log("Init lua state cost: {0}", Time.realtimeSinceStartup - time);
}
if (mainState == null)
{
mainState = this;
// MULTI_STATE Not Support
injectionState = mainState;
}
float time = Time.realtimeSinceStartup;
首先在初始化,会将实例化的对象分配给mainState静态成员。
InitTypeTraits();



C#层代码绑定类型检查。可以检查栈中指定元素是不是某种类型,具体用例如下,方便我们指定泛型就可以判断当前类型是不是泛型对应的类型:

C层代码如下:




lua_type则是在栈中指定位置取出StkId结构体,结构中包含tt类型,tt的值就是basic type中的定义。c#层LuaTypes和c层basic type中的定义是相同,这样绑定好了c层和c#的类型检查。
InitStackTraits();




从语法就能看出,Push是绑定将值压入lua栈中,Check是检查栈中指定元素是否是我们希望的类型,并转换为对应的类型(因为lua层统一返回的值类型都是double类型,所以需要转换),To则是取出栈中指定元素,并将它转换成我们希望的类型。
对应的c层代码也贴一下:




L = LuaNewState();
LuaException.Init(L);
stateMap.Add(L, this);


这一步没什么好说的了,就是启动lua官方提供的外接API,实例化虚拟机。并且绑定到C#层的LuaException,方便我们查看报错信息。
OpenToLuaLibs();

LUALIB_API void tolua_openlibs(lua_State* L)
{
initmodulebuffer();
luaL_openlibs(L);
int top = lua_gettop(L);
tolua_setluabaseridx(L);
tolua_opentraceback(L);
tolua_openpreload(L);
tolua_openubox(L);
tolua_openfixedmap(L);
tolua_openint64(L);
tolua_openuint64(L);
tolua_openvptr(L);
//tolua_openrequire(L);
luaL_register(L, "Mathf", tolua_mathf);
luaL_register(L, "tolua", tolua_funcs);
lua_getglobal(L, "tolua");
lua_pushstring(L, "gettag");
lua_pushlightuserdata(L, &gettag);
lua_rawset(L, -3);
lua_pushstring(L, "settag");
lua_pushlightuserdata(L, &settag);
lua_rawset(L, -3);
lua_pushstring(L, "version");
lua_pushstring(L, "1.0.7");
lua_rawset(L, -3);
lua_settop(L, top);
}
这里涉及到lua层的源码,之前未曾了解过,所以这里以贴代码为主,附加上我的猜测。
luaL_openlibs(L);

这一步应该绑定lua官方层的API,以table为例:

tolua_setluabaseridx(L);

tolua_opentraceback(L);

tolua_openpreload(L);

tolua_openubox(L);

tolua_openfixedmap(L);

tolua_openint64(L);

tolua_openuint64(L);

tolua_openvptr(L);

我相信只要写过一段时间lua业务的人,都会对以上代码代码有个模糊的猜测,这些函数的作用就是绑定我们在lua中常用到的全局表和方法,例如package全局表中,绑定了preload和loaded表,64int绑定了加减乘除等方法。
luaL_register(L, "Mathf", tolua_mathf);
luaL_register(L, "tolua", tolua_funcs);
lua_getglobal(L, "tolua");
lua_pushstring(L, "gettag");
lua_pushlightuserdata(L, &gettag);
lua_rawset(L, -3);
lua_pushstring(L, "settag");
lua_pushlightuserdata(L, &settag);
lua_rawset(L, -3);
lua_pushstring(L, "version");
lua_pushstring(L, "1.0.7");
lua_rawset(L, -3);
这些就是绑定一些tolua自定义的方法,以tolua举例:

你是否为tolua官方提供的Time.lua中的tolua.gettime不知道再哪里所实现而苦恼,没错,现在你知道了,它就实现在C层代码中。

那我们再顺便看看它的c层源码实现吧:

tolua_openlibs到此结束,现在我们继续回到C#层的源码吧。
ToLua.OpenLibs(L);

这里则重定义些lua的函数,你是否好奇过lua代码中print("xxx"),可以输出在Unity的Console当中,就是因为在这里进行了重写。然后这些重定义的函数我们该怎么在lua层去应用呢?我这里以tolua.toarray为例子,我们查看它的源码:

可以看到,它接收2个参数,第一个是lua数组,第二个是要转换的数据类型,那么我们在lua就这样实验:
function toarrayTest()
local lua_array = {1, 2, 3, 4, 5}
-- 注意必须转换为double,因为lua的number就是double类型
local cshare_array = tolua.toarray(lua_array, typeof("System.Double"))
print(cshare_array.Length)
end
兄弟们可以自己去试一下,是能够成功转换的。
除此之外,还有一个比较重要的是AddLuaLoader函数,它重定义package.loaders函数,为什么我们在lua中输入xxx.xxx.xx,我们可以找到对应的lua文件呢,就是通过这个自定义的Loader函数来实现的:

OpenBaseLibs();

这个函数,就是绑定Unity层对象类型到lua去,我们以System.Object为例看看它做了什么:
首先是BeginModule(null),查看c层源码,这么做的目的是将lua的global表压入栈中:

然后是BeginModule("System"),在lua中新建System表,并将它压入栈中:

然后是System_ObjectWrap.Register(this);

这个函数比较复杂,当然我们现在只用关注我标红的地方:


大概就是在lua层新建了一个Object表。然后我们以Equal方法为例:




可以看到,它的做法是获取Wrap文件中Equals方法的指针,将指针存入Object表中,这样我们在lua层就能直接与C#的API进行交互了。
InitLuaPath();

这个方法也比较简单,就不细讲了,就是初始化Lua文件的起始搜寻路径。然后有个点要注意下,tolua框架中不是自带一大堆lua文件嘛,这个在Editor下,如果你没有去修改起始搜寻地址的话,tolua框架会优先编译这个文件夹下的lua代码。当然我们也自己修改起始搜寻路径,将这些lua代码整合到跟业务lua代码在一起。毕竟打包出去后,还是得用我们业务lua代码,这些代码是不参与打包的。
当然怎么修改,我这里不做分享了,代码比较简单,兄弟们自己研究下吧。

总结
至此,LuaState初始化我们全部分析完毕,做个简单总结吧
- 初始化类型检查工具类
- 初始化Lua虚拟机
- 绑定异常检查工具
- 初始化Lua语法,比如table.sort()。以及自定义的lua语法,比如tolua.gettime()
- 重定义一些lua语法,比如print,loadfile,tolua.toarray之类的。
- 绑定基本C#模块到lua中(以lua表的形式),例如System.Object、System.String,UnityEngine.Object等等。
- 初始化Lua代码搜寻路径。
参考
[1] tolua源码分析(一)

浙公网安备 33010602011771号