本文是最后一篇C/C++与Lua交互的教程,在此之后,我们会结合Cocos2D-X来介绍Lua绑定。本文主要介绍如何绑定一个简单的C++类到Lua里面,并且提供Lua的面向对象访问方式。
绑定C++类
定义C++类
首先,我们定义一个Student类,它拥有名字(字符串类型)和年龄(整型),并且提供一些getter和setter,最后还提供了一个print方法.这里有Student类的定义和实现:Student.h和Student.cpp
编写绑定代码
首先,让我们编写在Lua里面创建Student对象的方法:
1 2
| Student **s = (Student**)lua_newuserdata(L, sizeof(Student*)); *s = new Student;
|
接下来是getName,setName,setAge,getAge和print方法的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| int l_setName(lua_State* L) { Student **s = (Student**)lua_touserdata(L, 1); luaL_argcheck(L, s != NULL, 1, "invalid user data"); luaL_checktype(L, -1, LUA_TSTRING); std::string name = lua_tostring(L, -1); (*s)->setName(name); return 0; }
int l_setAge(lua_State* L) { Student **s = (Student**)lua_touserdata(L,1); luaL_argcheck(L, s != NULL, 1, "invalid user data"); luaL_checktype(L, -1, LUA_TNUMBER); int age = lua_tonumber(L, -1); (*s)->setAge(age); return 0; }
int l_getName(lua_State* L) { Student **s = (Student**)lua_touserdata(L,1); luaL_argcheck(L, s != NULL, 1, "invalid user data"); lua_settop(L, 0); lua_pushstring(L, (*s)->getName().c_str()); return 1; }
int l_getAge(lua_State* L) { Student **s = (Student**)lua_touserdata(L,1); luaL_argcheck(L, s != NULL, 1, "invalid user data"); lua_settop(L, 0); lua_pushnumber(L, (*s)->getAge()); return 1; }
int l_print(lua_State* L) { Student **s = (Student**)lua_touserdata(L,1); luaL_argcheck(L, s != NULL, 1, "invalid user data"); (*s)->print(); return 0; }
|
从这里我们可以看到,userdata充当了C++类和Lua的一个桥梁,另外,我们在从Lua栈里面取出数据的时候,一定要记得检查数据类型是否合法。
注册C API到Lua里面
最后,我们需要把刚刚编写的这些函数注册到Lua虚拟机里面去。
1 2 3 4 5 6 7 8 9 10 11 12 13
| static const struct luaL_Reg stuentlib_f [] = { {"create", newStudent}, {"setName",l_setName}, {"setAge", l_setAge}, {"print", l_print}, {"getName",l_getName}, {"getAge", l_getAge}, {NULL, NULL} }; int luaopen_student (lua_State *L) { luaL_newlib(L, stuentlib_f); return 1; }
|
现在,我们把luaopen_student函数添加到之前的注册函数里面去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| static const luaL_Reg lualibs[] = { {"base", luaopen_base}, {"io", luaopen_io}, {"cc",luaopen_student}, {NULL, NULL} }; const luaL_Reg *lib = lualibs; for(; lib->func != NULL; lib++) { luaL_requiref(L, lib->name, lib->func, 1); lua_settop(L, 0); }
|
Lua访问C++类
现在,我们在Lua里面操作这个Student类。注意,我们绑定的每一个函数都需要一个student对象作为参数,这样使用有一点不太方便。
1 2 3 4 5 6
| local s = cc.create() cc.setName(s,"zilongshanren") print(cc.getName(s)) cc.setAge(s,20) print(cc.getAge(s)) cc.print(s)
|
最后,输出的结果为:
1 2 3
| zilongshanren 20 My name is: zilongshanren, and my age is 20
|
提供Lua面向对象操作API
现在我们已经可以在Lua里面创建C++类的对象了,但是,我们最好是希望可以用Lua里面的面向对象的方式来访问。
1 2 3 4
| local s = cc.create() s:setName("zilongshanren") s:setAge(20) s:print()
|
而我们知道s:setName(xx)就等价于s.setName(s,xx),此时我们只需要给s提供一个metatable,并且给这个metatable设置一个key为”__index”,value等于它本身的metatable。最后,只需要把之前Student类的一些方法添加到这个metatable里面就可以了。
我们可以在Registry里面创建这个metatable,然后给它取个名字做为索引,注意,为了避免名字冲突,所以这个名字一定要是独一无二的。
1 2 3 4 5 6
| int luaL_newmetatable (lua_State *L, const char *tname);
void luaL_getmetatable (lua_State *L, const char *tname);
void *luaL_checkudata (lua_State *L, int index,const char *tname);
|
接下来,我们要利用这3个C API来为我们的student userdata关联一个metatable.
修改绑定代码
首先,我们需要创建一个新的metatable,并把setName/getName/getAge/setAge/print函数设置进去。
下面是一个新的函数列表,一会儿我们要把这些函数全部设置到metatable里面去。
1 2 3 4 5 6 7 8
| static const struct luaL_Reg studentlib_m [] = { {"setName",l_setName}, {"setAge", l_setAge}, {"print", l_print}, {"getName",l_getName}, {"getAge", l_getAge}, {NULL, NULL} };
|
接下来,我们创建一个metatable,并且设置metatable.__index = matatable.注意这个cc.Student的元表会被存放到Registry里面。
1 2 3 4 5 6 7 8
| int luaopen_student (lua_State *L) { luaL_newmetatable(L, "cc.Student"); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); luaL_setfuncs(L, studentlib_m, 0); luaL_newlib(L, stuentlib_f); return 1; }
|
最后,我们记得在创建Student的时候把此元表与该userdata关联起来,代码如下:
1 2 3 4 5 6 7 8
| int newStudent(lua_State * L) { Student **s = (Student**)lua_newuserdata(L, sizeof(Student*)); *s = new Student; luaL_getmetatable(L, "cc.Student"); lua_setmetatable(L, -2); return 1; }
|
另外,我们在从Lua栈里面取出Student对象的时候,使用的是下面的函数
1
| Student **s = (Student**)luaL_checkudata(L,1,"cc.Student");
|
这个luaL_checkudata除了可以把index为1的栈上的元素转换为userdata外,还可以检测它是否包含“cc.Student”元表,这样代码更加健壮。
例如,我们之前的setName函数可以实现为:
1 2 3 4 5 6 7 8 9 10
| int l_setName(lua_State * L) { Student **s = (Student**)luaL_checkudata(L,1,"cc.Student"); luaL_argcheck(L, s != NULL, 1, "invalid user data"); luaL_checktype(L, -1, LUA_TSTRING); std::string name = lua_tostring(L, -1); (*s)->setName(name); }
|
这里有Student类的完整的新的绑定代码.
Lua访问C++类
现在,我们可以用Lua里面的面向对象方法来访问C++对象啦。
1 2 3 4 5 6
| local s = cc.create() s:setName("zilongshanren") print(s:getName()) s:setAge(20) print(s:getAge()) s:print()
|
这里输出的结果为:
1 2 3
| zilongshanren 20 My name is: zilongshanren, and my age is 20
|
管理C++内存
当Lua对象被gc的时候,会调用一个gc方法。因此,我们需要给绑定的C++类再添加一个gc方法。
首先是C++端的实现:
1 2 3 4 5 6 7 8 9 10
| static int auto_gc(lua_State *L) { Student **s = (Student**)luaL_checkudata(L,1,"cc.Student"); if( s ) { delete *s; } return 0; }
|
然后,添加注册函数:
1 2 3 4 5 6 7 8 9 10
| static const struct luaL_Reg studentlib_m [] = { {"__tostring",student2string}, {"setName",l_setName}, {"setAge", l_setAge}, {"print", l_print}, {"getName",l_getName}, {"getAge", l_getAge}, {"__gc", auto_gc}, {NULL, NULL} };
|
最后,我们在Stendent的构造函数和析构函数里面添加输出:
1 2 3 4 5 6 7 8 9 10
| Student::Student() :name("default") { cout<<"Student Contructor called"<<endl; }
Student::~Student() { cout<<"Student Destructor called"<<endl; }
|
接下来是Lua代码:
1 2 3 4 5 6 7 8 9 10 11
| local s = cc.create() s:setName("zilongshanren") s:setAge(20) s:print()
--当一个对象设置为nil,说明没有其它对应引擎之前cc.create创建出来的对象了,此时lua返回到c程序的时候会调用gc s = nil
--如果想在Lua里面直接手动gc,可以调用下列函数 --collectgarbage
|
最后,程序输出结果如下:
1 2 3
| Student Contructor called My name is: zilongshanren, and my age is 20 Student Destructor called
|
总结
本文主要介绍如何使用UserData来绑定C/C++自定义类型到Lua,同时通过引入MetaTable,让我们可以在Lua里面采用更加简洁的面向对象写法来访问导出来的类。下一篇文章,我们将介绍Cococs2D-X里面的tolua++及其基本使用方法。
PS:附上本文源代码,注意在LuaCocos2D-X工程里面。
Reference