tolua++使用简介(1.0.93)
1. tolua++编译
tolua++目前最新版本是1.0.93,可以在官方网站下载源码(http://www.codenix.com/~tolua/tolua++-1.0.93.tar.bz2)。
tolua++编译采用scons工具编译,在此之前还需要编译安装以下组件: lua: http://www.lua.org/ftp/lua-5.1.4.tar.gz python: http://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz scons: http://prdownloads.sourceforge.net/scons/scons-2.0.1.tar.gz
另外,目前tolua++无法自动识别lua5.1,所以需要自己创建一个custom.py放在tolua++目录下。custom.py内容如下(lua5.1的include和lib目录按照个人系统自行调整,这里分别是/usr/include/lua5.1和/usr/lib):
custom.py
CCFLAGS = ['-I/usr/include/lua5.1', '-O2', '-ansi']
LIBPATH = ['/usr/lib']
LIBS = ['lua5.1', 'dl', 'm']
tolua_bin = 'tolua++5.1'
tolua_lib = 'tolua++5.1'
TOLUAPP = 'tolua++5.1'
之后就可以直接在tolua++目录下运行如下命令编译安装:
scons all
scons prefix=/usr install
2. tolua++使用
使用tolua++转换需要提供一个pkg文件。这个pkg文件包含了我们需要导出到lua的常量、变量、函数等,可以在头文件的基础上修改,实践中发现一般只需要把头文件的__cplusplus宏去掉即可。另外头文件中如果有使用在其它头文件中定义的typedef也需要一并添加进去,不然会把一些内置类型的typedef当成user type处理。
下面结合一个简单例子说明,假设我们有如下.h和.c文件: test.h
int foo(int a, int b);
test.c
int foo(int a, int b)
{
return a+b;
}
得到的pkg文件如下: test.pkg
int foo(int a, int b);
之后运行如下命令生成代码:
tolua++5.1 –o test_lua.c test.pkg
test.c内会包含一个形如int tolua_test_open (lua_State* tolua_S)的函数,这个函数用于初始化我们生成的c/c++模块,必须在lua解释器内调用。
我们的lua解释器实现如下: main.c
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
extern int tolua_test_open (lua_State* tolua_S);
int main (void)
{
int error;
lua_State *L = luaL_newstate();
luaL_openlibs(L);
tolua_test_open(L); // 初始化模块
error = luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0);
if (error)
{
fprintf(stderr, "%s\n", lua_tostring(L, -1));
lua_pop(L, 1);
}
lua_close(L);
return 0;
}
编译解释器时需要同时连接liblua和libtolua++:
gcc -O2 -Wall -o main main.c test.c test_lua.c -llua -ltolua++5.1
这样就得到了一个支持我们从c/c++导出的模块的简单lua解释器。
我们可以在test.lua内调用导出的c/c++函数,如下所示: test.lua
r = foo(4, 5)
print(r)
3. 注意事项
tolua++转换对于大部分情况都支持较好,但还是有一些需要我们自己进行手动调整。实践中主要遇到以下几种情况:
a. 回调函数
tolua++会把回调函数当成user type处理,这样在lua脚本中要求传递的将是一个c/c++函数,而一般我们这时都是在lua脚本中定义lua回调函数,因此需要修改回调函数的转换。
我们可以使用luaL_ref函数来获得一个指向lua值的引用,然后将该值当成lua回调函数的索引保存,需要调用lua回调函数的时候使用lua_rawgeti函数将其压入栈中,再使用lua_pcall函数调用。最后用luaL_unref释放值和引用。
以下是一个简单的例子: test.pkg
typedef int (*handler)(int event);
void register_event_handler(handler h);
void unregister_event_handler(void);
tolua++转换生成的test_lua.c中的对应函数是tolua_test_register_event_handler00和tolua_test_unregister_event_handler00,我们修改如下: test_lua.c
static lua_State *__lua_state = NULL;
static int __lua_event_handler;
static int event_handler_wrapper(int event)
{
int result;
if (!__lua_state)
return -1;
lua_rawgeti(__lua_state, LUA_REGISTRYINDEX, __lua_event_handler);
lua_pushinteger(__lua_state, event);
if (lua_pcall(__lua_state, 1, 1, 0))
{
fprintf(stderr, “%s\n”, lua_tostring(__lua_state, -1));
lua_pop(__lua_state, 1);
return -1;
}
result = lua_checkinteger(__lua_state, -1);
lua_pop(__lua_state, 1);
return result;
}
/* function: register_event_handler */
#ifndef TOLUA_DISABLE_tolua_test_register_event_handler00
static int tolua_test_register_event_handler00(lua_State* tolua_S)
{
__lua_state = tolua_S;
__lua_event_handler = luaL_ref(tolua_S, LUA_REGISTRYINDEX);
register_event_handler(event_handler_wrapper);
return 0;
}
#endif //#ifndef TOLUA_DISABLE
/* function: unregister_event_handler */
#ifndef TOLUA_DISABLE_tolua_test_unregister_event_handler00
static int tolua_test_unregister_event_handler00(lua_State* tolua_S)
{
unregister_event_handler();
luaL_unref(__lua_state, LUA_REGISTRYINDEX, __lua_event_handler);
__lua_event_handler = 0;
__lua_state = NULL;
return 0;
}
#endif //#ifndef TOLUA_DISABLE
lua脚本使用例子如下: test.lua
register_event_handler(function(e) print(e) end)
unregister_event_handler()
b. 变参函数
tolua++不支持变参函数的转换,必须手动转换。目前没有太好的通用解决方法,只能根据具体函数实现枚举出各种情况依次传递参数。
c. 结构参数
结构参数经tolua++转换以后会生成同名的user type,但是这种类型的变量的创建和销毁方法需要我们自己提供。
可以直接在pkg文件添加相应的创建和销毁函数声明,让tolua++转换成对应的lua函数。当然具体实现需要我们自己提供。 test.pkg
struct bar
{
int a;
double b;
}
bar *bar_create(int a, double b); // 增加创建函数声明
void bar_destroy(bar *b); // 增加销毁函数声明
d. 指针参数
这里先讨论一级指针参数的情况,可以分为字符串、内置类型指针、结构指针3种情况。tolua++对字符串参数的处理会受到typedef的影响,在后面会再提到;对内置类型指针和结构指针都可以正确转换。
要注意的地方是,由于lua是没有指针类型的,我们无法在lua脚本中创建一个指针。虽然我们可以把指针类型定义成user type再导出到lua中,但是一般没有必要这样处理。tolua++的做法是在lua脚本中直接传递值参数,如下面例子: test.pkg
void get_value(int *v);
test.lua
v = 0
v = foo(v)
v = foo(0)
v = foo(nil) -- 定义TOLUA_RELEASE宏情况下可行
tolua++会把参数中的指针类型参数的解引用后的值作为函数值返回,我们可以从返回值拿到希望获取的值。
二级指针的情况,通常是希望由函数内部分配内存获取地址,这种情况下tolua++对内置类型转换会出问题,对字符串参数还是会受typedef有不同表现,对结构则可以正确转换。内置类型转换的表现是当作一级指针处理: test.pkg
void foo(int **p);
test_lua.c
/* function: foo */
#ifndef TOLUA_DISABLE_tolua_test_foo00
static int tolua_test_foo00(lua_State* tolua_S)
{
......
int p = ((int)tolua_tonumber(tolua_S,1,0));
{
foo(&p);
tolua_pushnumber(tolua_S,(lua_Number)p);
}
......
}
#endif //#ifndef TOLUA_DISABLE
这里可以按照需求有以下2种修改方法: test_lua.c
/* function: foo */
#ifndef TOLUA_DISABLE_tolua_test_foo00
static int tolua_test_foo00(lua_State* tolua_S)
{
......
int *p;
{
foo(&p);
// tolua_pushnumber(tolua_S,(lua_Number)p);
// tolua_pushnumber(tolua_S,(lua_Number)(*p));
}
......
}
#endif //#ifndef TOLUA_DISABLE
第一种方法是直接在在lua脚本中用number类型存储获取到的地址,但同时可能需要修改所有涉及传入该值的函数;第二种方法是返回值,一般不用修改其他函数。
至于多级指针的情况,目前代码中极少出现,暂时应该不用处理。
e. 数组参数
对于数组参数,必须写明数组维数,也不能写成指针形式,否则tolua++无法正确识别成数组参数处理。 test.pkg
void foo(int array[10]); // 正确
void foo(int array[]); // 错误
void foo(int *array); // 错误
void foo(int array[n], int n); // 正确,维数可以不是常量
但是对于字符数组参数,tolua++仍然不能正确转换,会当作字符串数组参数处理,然而如果对char作了typedef反而能转换正确。我们目前的代码由于基本都是作了typedef,因此一般不会出现转换错误的情况。
另外要注意的一点是,要求数组参数的函数导出到lua之后,在lua脚本中同名函数要求的参数是table类型。字符数组参数同样也是要求table类型而不是string类型。如果考虑到调用的便利性,可能还是需要修改函数实现。
f. typedef
上面提到过,一些内置类型在typedef定义之后,会影响tolua++的转换。这里主要说明typedef以后转换出错的情况,目前遇到的主要是对char进行typedef以后传递字符串参数出现的问题。
如下2个函数,第一个函数的参数能够正常转换成字符串参数,第二个函数的参数则会被当成char字符参数处理。 test.pkg
typedef char s8;
void foo1(char *p);
void foo2(s8 *p);
test_lua.c
/* function: foo1 */
#ifndef TOLUA_DISABLE_tolua_test_foo100
static int tolua_test_foo100(lua_State* tolua_S)
{
......
char* p = ((char*) tolua_tostring(tolua_S,1,0));
{
foo7(p);
}
......
}
#endif //#ifndef TOLUA_DISABLE
/* function: foo2 */
#ifndef TOLUA_DISABLE_tolua_test_foo200
static int tolua_test_foo200(lua_State* tolua_S)
{
......
char p = (( char) tolua_tonumber(tolua_S,1,0));
{
foo8(&p);
tolua_pushnumber(tolua_S,(lua_Number)p);
}
......
}
#endif //#ifndef TOLUA_DISABLE
由于我们目前的代码基本上都对类型作了typedef,因此这种转换错误几乎是不可避免的,只能进行手动修改。
另外typedef的不同写法也可能对tolua++转换有影响。 test.pkg
typedef struct s_bar
{
int a;
double b;
}
bar;
typedef struct s_bar bar_t;
上面的代码经tolua++转换以后,会生成s_bar和bar2种user type,而bar_t不会生成,会直接当作s_bar处理。如果我们代码中包含struct s_bar和bar类型的参数,在经tolua++转换以后会被当成2种不同的类型,在传递参数的时候不能混用。
blog comments powered by Disqus