欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-16 23:48:29
本文是最后一篇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类的定义和实现:student.hstudent.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工程里面。