当前位置: 移动技术网 > IT编程>脚本编程>Lua > Lua教程(六):绑定一个简单的C++类

Lua教程(六):绑定一个简单的C++类

2017年12月08日  | 移动技术网IT编程  | 我要评论
本文是最后一篇c/c++与lua交互的教程,在此之后,我们会结合cocos2d-x来介绍lua绑定。本文主要介绍如何绑定一个简单的c++类到lua里面,并且提供lua的面向

本文是最后一篇c/c++与lua交互的教程,在此之后,我们会结合cocos2d-x来介绍lua绑定。本文主要介绍如何绑定一个简单的c++类到lua里面,并且提供lua的面向对象访问方式。

绑定c++类

定义c++类

首先,我们定义一个student类,它拥有名字(字符串类型)和年龄(整型),并且提供一些getter和setter,最后还提供了一个print方法.这里有student类的定义和实现:和

编写绑定代码

首先,让我们编写在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工程里面。

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网