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();

image

image

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

C层代码如下:
image

image

image

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


            InitStackTraits();

image

image

image

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

image

image

image


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

image

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


            OpenToLuaLibs();  

image

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);

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


    tolua_setluabaseridx(L);

image

    tolua_opentraceback(L);

image

    tolua_openpreload(L);

image

    tolua_openubox(L);

image

    tolua_openfixedmap(L);

image

    tolua_openint64(L);

image

    tolua_openuint64(L);

image

    tolua_openvptr(L);

image
我相信只要写过一段时间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举例:
image
你是否为tolua官方提供的Time.lua中的tolua.gettime不知道再哪里所实现而苦恼,没错,现在你知道了,它就实现在C层代码中。
image
那我们再顺便看看它的c层源码实现吧:
image
tolua_openlibs到此结束,现在我们继续回到C#层的源码吧。


            ToLua.OpenLibs(L);

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


            OpenBaseLibs();

image
这个函数,就是绑定Unity层对象类型到lua去,我们以System.Object为例看看它做了什么:
首先是BeginModule(null),查看c层源码,这么做的目的是将lua的global表压入栈中:
image
然后是BeginModule("System"),在lua中新建System表,并将它压入栈中:
image
然后是System_ObjectWrap.Register(this);
image
这个函数比较复杂,当然我们现在只用关注我标红的地方:
image

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

image

image

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


            InitLuaPath();

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


总结

至此,LuaState初始化我们全部分析完毕,做个简单总结吧

  1. 初始化类型检查工具类
  2. 初始化Lua虚拟机
  3. 绑定异常检查工具
  4. 初始化Lua语法,比如table.sort()。以及自定义的lua语法,比如tolua.gettime()
  5. 重定义一些lua语法,比如print,loadfile,tolua.toarray之类的。
  6. 绑定基本C#模块到lua中(以lua表的形式),例如System.Object、System.String,UnityEngine.Object等等。
  7. 初始化Lua代码搜寻路径。

参考

[1] tolua源码分析(一)

posted @ 2025-04-18 15:44  陈侠云  阅读(305)  评论(0)    收藏  举报
//雪花飘落效果