Lua教程(六):绑定一个简单的C++类
本文是最后一篇c/c++与lua交互的教程,在此之后,我们会结合cocos2d-x来介绍lua绑定。本文主要介绍如何绑定一个简单的c++类到lua里面,并且提供lua的面向对象访问方式。
绑定c++类
定义c++类
首先,我们定义一个student类,它拥有名字(字符串类型)和年龄(整型),并且提供一些getter和setter,最后还提供了一个print方法.这里有student类的定义和实现:student.h和student.cpp
编写绑定代码
首先,让我们编写在lua里面创建student对象的方法:
student **s = (student**)lua_newuserdata(l, sizeof(student*)); // lua will manage student** pointer
*s = new student; //这里我们分配了内存,后面我们会介绍怎么让lua在gc的时候释放这块内存
接下来是getname,setname,setage,getage和print方法的定义:
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虚拟机里面去。
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函数添加到之前的注册函数里面去:
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++)
{
//注意这里如果使用的不是requiref,则需要手动在lua里面调用require "模块名"
lual_requiref(l, lib->name, lib->func, 1);
lua_settop(l, 0);
}
lua访问c++类
现在,我们在lua里面操作这个student类。注意,我们绑定的每一个函数都需要一个student对象作为参数,这样使用有一点不太方便。
local s = cc.create()
cc.setname(s,"zilongshanren")
print(cc.getname(s))
cc.setage(s,20)
print(cc.getage(s))
cc.print(s)
最后,输出的结果为:
zilongshanren
20
my name is: zilongshanren, and my age is 20
提供lua面向对象操作api
现在我们已经可以在lua里面创建c++类的对象了,但是,我们最好是希望可以用lua里面的面向对象的方式来访问。
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里面就可以了。
metatable
我们可以在registry里面创建这个metatable,然后给它取个名字做为索引,注意,为了避免名字冲突,所以这个名字一定要是独一无二的。
//创建名字为tname的metatable并放在当前栈顶,同时把它与registry的一个key为tname的项关联到一起
int lual_newmetatable (lua_state *l, const char *tname);
//从当前栈顶获取名字为tname的metatable
void lual_getmetatable (lua_state *l, const char *tname);
//把当前栈index处的userdata取出来,同时检查此userdata是否包含名字为tname的metatable
void *lual_checkudata (lua_state *l, int index,const char *tname);
接下来,我们要利用这3个c api来为我们的student userdata关联一个metatable.
修改绑定代码
首先,我们需要创建一个新的metatable,并把setname/getname/getage/setage/print函数设置进去。 下面是一个新的函数列表,一会儿我们要把这些函数全部设置到metatable里面去。
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里面。
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关联起来,代码如下:
int newstudent(lua_state * l)
{
student **s = (student**)lua_newuserdata(l, sizeof(student*)); // lua will manage student** pointer
*s = new student;
lual_getmetatable(l, "cc.student");
lua_setmetatable(l, -2);
return 1;
}
另外,我们在从lua栈里面取出student对象的时候,使用的是下面的函数
student **s = (student**)lual_checkudata(l,1,"cc.student");
这个lual_checkudata除了可以把index为1的栈上的元素转换为userdata外,还可以检测它是否包含“cc.student”元表,这样代码更加健壮。 例如,我们之前的setname函数可以实现为:
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++对象啦。
local s = cc.create()
s:setname("zilongshanren")
print(s:getname())
s:setage(20)
print(s:getage())
s:print()
这里输出的结果为:
zilongshanren
20
my name is: zilongshanren, and my age is 20
管理c++内存
当lua对象被gc的时候,会调用一个__gc方法。因此,我们需要给绑定的c++类再添加一个__gc方法。
首先是c++端的实现:
然后,添加注册函数:
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的构造函数和析构函数里面添加输出:
student::student()
:name("default")
{
cout<<"student contructor called"<<endl;
}
student::~student()
{
cout<<"student destructor called"<<endl;
}
接下来是lua代码:
local s = cc.create()
s:setname("zilongshanren")
s:setage(20)
s:print()
--当一个对象设置为nil,说明没有其它对应引擎之前cc.create创建出来的对象了,此时lua返回到c程序的时候会调用gc
s = nil
--如果想在lua里面直接手动gc,可以调用下列函数
--collectgarbage
最后,程序输出结果如下:
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工程里面。
上一篇: Lua中字符串(string)浅析