1.简介
Tolua++是一个编程语言文本转换工具,主要用来将C/C++文件转化成lua需要的接口形式。即把大量的函数转成static int XXX(lua_State* tolua_S)形式,以供注册到lua里面。
Tolua++通过pkg文件来导出需要的类型,函数,对象。
Tolua –o test.c tarray.pkg
tarray.pkg里面是对应的要导出的内容,通常是用相应的.h文件通过pkg的编写规则改写过来。将生成的test.c与程序链接并添加少量代码,即可在lua脚本中访问导出的类和函数。具体介绍请看: http://www.codenix.com/~tolua/tolua++.html
2.实现原理
C++在进行函数调用的时候是this指针+函数地址
Lua提供用户自定义的userdata
一般lua中持有c++对象是使用userdata来实现的(userdata 类型用来将任意 C 数据保存在 Lua 变量中. 这个类型相当于一块原生的内存, 除了赋值和相同性判断, Lua 没有为之预定义任何操作.通过使用 metatable (元表), 程序员可以为 userdata 自定义一组操作. metatable 中还可以定义一个函数gc让 userdata 作垃圾收集时调用它。
因此,metatable可以用来模拟C++里面的函数,通过替换它来实现函数,类成员的查找。Userdata可以很方便的获取到转换成C++里面this指针。通过this指针+类的函数地址即可调用C++里面的类成员函数。
3.实现
A.tolua++整体目录结构
src的目录中有
Src\bin下面是tolua++ 可执行的源码,主要是处理pkg文件生成.c文件
Src\lib 是tolua++ lib的源码,主要是为生成的.c文件里面的函数,类,对象映射给lua调用。
Bin的目录结构
Tolua.c文件主要是接收用户的命令行参数进行处理。并将相应参数的值放在lua栈中,存放在flags的表中,在lua的代码中也可以见到。后面根据宏TOLUA_SCRIPT_RUN进行判断是调用
C++的代码还是转调用lua代码。当修改了lua代码我们想马上看到效果的时候,应该定义这个宏。默认情况下,该宏是没有定义的。意味着将会调用C++的代码,在toluabind文件里面。
Toluabind.c这个文件也是由tolua++这个工具生成的,它将src\bin\lua下的lua代码生成这个c文件。将lua代码生成.c文件,主要是为了效率。Toluabind.c里面的代码对应于src\bin\lua\all.lua,主要加载src\bin\lua下面的lua代码。
Lib的目录结构
Tolua_event 主要提供模块,类对应的lua元方法。
Tolua_is 主要提供基础类型和用户定义类型的类型判断。
Tolua_map 很重要的一个类,添加模块,类,函数,成员,包括类的继承以及一些全局方法。
Tolua_push 提供压入基础类型,用户定义类型给lua
Tolua_to提供从lua中的对象还原到基础类型或者用户定义类型。
B.导出类型
类型首先会被用tolua_usertype函数进行注册,内部也就是创建一个表。
C.导出成员变量
成员变量主要通过tolua_variable(tolua_S,"x",tolua_get_Point_x,tolua_set_Point_x);来导出,tolua_variable将该变量的get和set方法分别放进了.get和.se表中。
D.导出函数
函数会通过tolua_function导出,直接放在当前的模块表或者类表中。
E.导出对象
对象会通过tolua_cclass导出
TOLUA_API void tolua_cclass (lua_State*L, const char* lname, const char* name, const char* base, lua_CFunction col)
{
…
mapinheritance(L,name,base); 设置继承关系,默认继承自base
mapinheritance(L,cname,name);
…
luaL_getmetatable(L,name);
lua_rawset(L,-3); 设置类的metatable给module
}
F.关联
在创建一个元表的时候,会调用tolua_newmetatable方法,代码如下:
static inttolua_newmetatable (lua_State*L, char* name)
{
int r = luaL_newmetatable(L,name);
…
if (r)
tolua_classevents(L); /* set meta events */
lua_pop(L,1);
return r;
}
会调用tolua_classevents,这个函数会向当前的元表注册新的函数
lua_pushstring(L,"__index");
lua_pushcfunction(L,class_index_event);
lua_rawset(L,-3);
lua_pushstring(L,"__newindex");
lua_pushcfunction(L,class_newindex_event);
lua_rawset(L,-3);
…
__index对应的函数为class_index_event,当我们查找某个成员的时候,会路由到class_index_event中,在class_index_event中会向.get表进行查询,看看是否有相应的函数,这个函数在生成的.c文件中已经被注册了,所以可以正常访问。
访问函数也是一样,通过
lua_pushstring(L,"__call");
lua_pushcfunction(L,class_call_event);
lua_rawset(L,-3);
路由到,class_call_event,,class_call_event直接查.call表找类的处理函数。
G综上,tolua++的内部主要靠表结构+替换元表的机制来实现在lua层模拟C++对象。
4.tolua++提供的机制
A.Tolua++不仅仅可以处理pkg文件,也可以处理lua文件。所以在使用tolua++的过程中,可以通过-L属性来加载lua代码的文件,实现对指定类的补丁。
B.tolua++支持在处理pkg代码的各个阶段进行hook,可实现function preparse_hook(package)等lua接口,详见
http://www.codenix.com/~tolua/tolua++.html
C. tolua++支持定制指定类的pushusertype,isusertype,tousertype操作
_is_functions['Vector3'] = 'custom_is_vector3' -- checks for a 3d vector
-- (either userdata, or a table with 3 values)
_to_functions['Vector3'] = 'custom_to_vector3' -- convertes the eventual table to a Vector3
_base_push_functions['Widget'] = 'custom_push_widget' -- pushes anything that inherits from Widget
D.我们可以通过修改tolua++中的lua代码提供更多的机制,如自定义gc等。
5.tolua++对pkg格式的解析
调用 Package (name,fn)函数对文件进行解析,启动doit函数
push(p)
pre_output_hook(p)
pop()
p:preamble() //生成前言部分
p:supcode() //生成代码
push(p)
pre_register_hook(p)
pop()
p:register() //生成注册部分
push(p)
post_output_hook(p)
pop()