tolua++ 参考手册
--刘源霖 (译)
1 前言
本文是对tolua++官网使用手册的翻译,如果读者在阅读的过程中什么疑问可参考官方使用手册。在本文中不会添加任何译者的理解,完全是安装文章笔者的原文进行翻译。
2 概述
tolua++ 是tolua的一个扩展版本,是一个将C/C++代码集成到lua中的工具。Tolua++增加了面向C++的新功能和bug修正,比如支持std::string作为基础类型(可以通过命令行参数开关),支持模板类。
Tolua++工具集成C/C++代码到lua非常的简单方便,tolua能够基于一个简单的头文件(可以从多个头文件中提取)为lua访问C/C++功能自动生成绑定代码。通过使用lua API 和一些标记方法,tolua能为lua提供C/C++中的常数,外部变量,函数,类,方法。
该使用手册是针对tolua++1.0版本,实现是基于lua5.0和tolua5.0。如果想了解相对旧版本的兼容性细节可以访问tolua++官网。下面部分我将描述如何使tolua,如果发现任何错误或者建议可以联系tolua++官方团队。
3 tolua++如何工作
使用tolua,我们需要创建一个pkg文件,即一个简单的C/C++头文件,该文件包括我们想要绑定到lua中的常数,外部变量,函数,类,方法。然后使用tolua解析pkg文件,生成一个自动绑定C/C++代码到到lua的C/C++文件,只要我们将生成的文件链接到我们的应用程序中,就能在lua中访问我们在pkg文件中指定的C/C++代码,即我们定义的常数,外部变量,函数,类,方法。一个pkg文件能够包含正规的C/C++头文件、其他pkg文件以及lua文件。
让我们一个例子开始,假定我们指定绑定下面类似C 头文件的pkg文件到lua中:
a.pkg
int foo(lua_State *L)
#define FALSE 0
#define TRUE 1
enum {
POINT = 100,
LINE,
POLYGON
}
Object* createObejct (int type);
void drawObject (Object* obj, double red, double green, double blue);
int isSelected (Object* obj);
使用tolua++解析a.pkg生成一个自动绑定上面代码的C文件,链接到我们的应用中,然后我们可以在lua中这样使用绑定的C 代码, 用一个实例来表示:
a.lua
...
myLine = createObject(LINE)
...
if isSelected(myLine) == TRUE then
drawObject(myLine, 1.0, 0.0, 0.0);
else
drawObject(myLine, 1.0, 1.0, 1.0);
end
...
同样,导入一个类C++的头文件:
b.pkg
#define FALSE 0
#define TRUE 1
class Shape
{
void draw (void);
void draw (double red, double green, double blue);
int isSelected (void);
};
class Line : public Shape
{
Line (double x1, double y1, double x2, double y2);
~Line (void);
};
用tolua解析这个文件,将会自动生成一个C++文件,该文件绑定上面的code到lua中,导入的代码可以下面这样有效的使用:
...
myLine = Line:new (0,0,1,1)
...
if myLine:isSelected() == TRUE then
myLine:draw(1.0,0.0,0.0)
else
myLine:draw()
end
...
myLine:delete()
...
提供给tolua解析的这个pkg文件(通常用扩展名.pkg表示)不是一个真正的C/C++头文件,但是是真正C/C++头文件的一个更简洁的版本。Tolua没有实现完全的解析C/C++代码,但是它能解析一些的声明,这些声明是用来描述我们要导入到lua的功能。正规的头文件可以包含在pkg文件中,tolua将从头文件中提取用户指定的代码进行解析。
4 如何使用tolua
Tolua 由两部分组成:一个可执行文件和一个库文件。这个可执行文件就是一个解析器,它读取pkg文件进行解析,输出一个对应的C/C++文件,这个C/C++实现了自动绑定pkg文件中指明的code到lua中,让lua中能够正常的访问。如果这个pkg文件是一个类似于C++的头文件(即包含类的定义),那么将自动生成一个C++文件,如果这个pkg文件是一个类似于C的头文件,那么将自动生成一个C文件,tolua命令支持带参数运行,运行“tolua –h” 可以查看当前tolua命令支持的参数集。例如,利用tolua解析一个名为myfile.pkg的文件生成其绑定代码到myfile.c文件中,我们可以用命令:
tolua –o myfile.c myfile.pkg
这生成的文件必须编译,并且链接到我们的应用程序中,这样才能为lua提供访问,每一个解析的文件被当作一个包导入到lua中。默认情况下,这个包名就是导入文件的根文件名(在上一个例子中包名就是myfile)。用户还可以指定不同报名:
tolua –n pkgname –o myfile.c myfile.pkg
这个包必须被明确的初始化。为了在C/C++代码中初始化这个包,我们必须声明和调用这个初始化函数,这个初始化函数被定义为:
Int tolua_pkgname_open(lua_State *);
其中pkgname就是我们要初始化的包名。如果我们使用的是C++,我们还可以选择自动初始化:
Tolua –a –n pkgname –o myfile.c myfile.pkg
这中情况下,这个初始化函数将会自动被调用,但是如果我们计划使用多个lua states,则不能使用自动初始化,因为那些在C++中被初始化的静态变量的顺序没有被定义。
这个open函数的原型也可以选择输出到一个头文件中,这个头文件名字可以用—H 选项指定。
tolua自动生成的绑定代码使用了tolua库中的函数集,所以这个库必须被链接到我们的应用程序中。自动生成的绑定代码中必须包含头文件tolua.h。
一个应用程序可以不绑定任何包,直接使用tolua提供的面向对象框架(具体见通用函数导出章节),但是必须调用tolua的初始化函数(这个函数在任何的包初始化函数中被调用):
Int tolua_open(void);
5 基本概念
使用tolua的第一步是创建一个pkg文件。以一个真实的头文件开始,我需要按照tolua能够解析的格式去清理我们项目想要导入到lua中的功能。这tolua能够解析的格式是以下方式描述的一个简单的C/C++声明。
包含文件
一个pkg文件可能包含其他pkg文件,其格式如下:
$pfile “include_file”
一个pkg文件可能包含正规的C/C++头文件,用hfile 或者cfile指令:
$cfile “example.h”
在这种情况下,tolua将会提取在“tolua begin” 和 “tolua_end”之间的代码,或者用“tolua_export”指定的单行代码。以下面的C++头文件为例:
#ifndef EXAMPLE_H
#define EXAMPLE_H
Calss Example { //tolua_export
private:
string name;
int number;
public:
void set_number(int number);
//tolua_begin
string get_name();
int get_number();
};
//tolua_end
#endif
在这个例子中,不被tolua支持的代码(类的私有变量)和函数set_number 将会被舍弃,不会包含到pkg文件中。
最后,lua文件也可以包含在pkg文件中,使用“$lfile”:
$lfile “example.lua”
tolua++ 扩展:
从版本1.0.4开始新增加了一个包含资源文件的扩展方法,使用命令:$ifile:
$ifile "filename"
Ifile 通常在文件名的后面使用附件参数, 例如:
$ifile "widget.h", GUI
$ifile "vector.h", math, 3d
Ifile的默认行为是包含文件的原因内容,但是,这包含文件的内容和扩展参数在包含进pkg文件前先要调用include_file_hook函数(细节前参考定制tolua++章节)。
5.1 基本类型
Tolua 自动映射C/C++j接班类型到LUA的基本类型,因此:char, int , float,double被映射到Lua的number类型;char * 被映射成string;void * 被映射成userdata;每个类型类型可能被加上前缀修饰语(unsigned, static, short, const, etc.);然而,假如基本类型前加上const,tolua将会忽略它。因此,假如我们传递一个常数基本类型到lua,然后从lua再传回给C/C++代码时将会变成一个非常数类型,这种常数类型到非常数类型的转换将被默认完成。
在C/C++函数中也能明确的使用lua对象,因此,“lua_Object”类被看作一个基本类型,任何的Lua 值都可以用lua_Object表示。
Tolua++ 扩展:
C++类型string 被看作基本类型,以及可以被作为一个值传递到lua中(使用c_str()方法)。这个功能可以通过命令行参数-S关闭。
5.2 用户定义类型
所有在pkg文件中出现过的其他的类型被看作用户定义类型,他们被映射成lua中标记的userdata类型。Lua仅仅能够存储用户定义类型的指针。尽管如此,tolua++会自动生成一些必要的步骤来处理引用和值。举个例子,假如在函数或者方法中返回了一个用户定义类型,当返回它到lua时,tolua分配一个克隆的对象以及设置其垃圾回收标记方法,以便在lua不再使用时自动回收分配对象。
对于用户定义类型,常量是被保存的。因此,传递一个非常量的用户定义类型到一个需要参数是常量类型的函数中时将会产生一个类型不匹配的错误(type mismatch error)
5.3 NULL and nil
C/C++ NULL或者0指针被映射成lua中的nil类型,相反的,nil可以给任何一个C/C++指针。将char* ,void*以及指针转换成用户定义类型是有效的。
5.4 Typedefs
Tolua 也支持简单的类型定义(typedef),一个类型被重新定义后,其后任何出现的地方都会被tolua映射成基本类型。这是非常有用的,每个包可以通过C/C++的基础类型重新定义自己的类型。例,某个包可以定义类型real 当作 double类型,这样,real就可以在pkg文件中定义变量的类型了。但是在我们使用前必须包含如下的定义:
typedef double real;
否则,real将会被解释成一个用户定义类型,而不是lua的number类型。
5.5 包含真正的头文件
在pkg文件中,我们必须指定包含真正的头文件,以至于tolua生成的代码中能够正常的访问我们要绑定到lua中的常量,变量,函数,类等。在pkg文件中任何以$符号开始的一行(除$[hclp]file, $[ , $] 开头行)将会不作任何修改的插入到生成的绑定代码中,但是要去除$符号。我们使用这个功能来包含真正的头文件,所以,通常在pkg文件的开始位置我们会用$符号包含我们指定的头文件,这些头文件是我们pkg文件的基础。
/* specify the files to be included */
$#include "header1.h" // include first header
$#include "header2.h" // include second header
从上面的代码可以看出,tolua也接受注释,注释使用C/C++习惯方式。
同样可以注意到使用$hfile 或者 $cfile 不需要使用include这种方式,这些被toulua自动完成了。
在接下的章节中,我们将描述如何指定我们想要绑定到lua中的C/C++代码, 这格式是一个简单的有效的C/C++声明。
6 绑定常量
为了绑定常量,tolua支持define和enum,对于define的支持其格式如下:
#define NAME [VALUE]
VALUE 是可选的,将上面的代码包含在pkg文件中,tolua生成的绑定代码允许lua中把NAME当作一个全局变量使用,并且其值和C/C++中的值一样。但是仅仅只支持数值常量。
Tolua++ 扩展:所以其他预处理将会被忽略。
对于enum支持格式如下:
enum {
NAME1 [ = VALUE1 ] ,
NAME2 [ = VALUE2 ] ,
...
NAMEn [ = VALUEn ]
};
同样的,tolua创建了多个全局变量,以及他们对应的值。
7 绑定外部变量
全局的外部变量可以被导出,在pkg文件中的他们的指定格式如下:
[extern] type var;
Tolua绑定此声明到Lua的全局变量中,因此在Lua中我们能够自然的访问C/C++变量,假如这个变量是一个非常量,我们在lua中为其赋新值。全局数组也可以绑定到乱中,数组可以是任何类型,与之对应的lua对象是table,table的以数字做索引,只是table中时从1开始,而数组中是从0开始。例:
double v[10];
tolua++扩展:外部变量我们可以用tolua_readonly 修饰(见附件功能章节)
8 绑定函数
函数的指定按照平常的C/C++函数声明:
type funcname (type1 par1[, type2 par2[,...typeN parN]]);
这返回的类型可以是空,意味着没有值返回。函数也可以没有参数,参数链表用void表示。参数类型必须准信之前的说明的规则。Tolua创建一个Lua 函数 绑定这个C/C++函数,在lua中调用这个函数时,参数类型必须匹配,否则toua将会生成错误信息,并且报告那个参数有错误。假如参数的名字是省略的,tolua将会自动为期命名,但是其类型必须是基本类型或者之前已经定义的用户类型。
8.1 数组
Tolua也能够处理函数或者方法参数是数组时,数组参数对应lua中的table,数组的长度必须提前预定,例:
void func (double a[3]);
对tolua上面是一个有效的声明,绑定后在lua中可以直接调用该函数,例:
p = {1.0,1.5,8.6}
func (p)
数组的维数不一定是一个参数表达式,可以任何一个可以在运行计算的表达式,例:
void func (int n, int m, double image[n*m]);
但是上面这种使用方法,tolua是使用动态分配法来绑定函数的,所以其性能要低一些。
尽管规定了数组的大小,但是我们必须知道所有传入实际C/C++函数的数组变量在绑定函数都是局部变量,所以,如果被调用的C/C++函数如果要保存数组的指针在函数调用后使用,那么这绑定代码将会出现异常。
8.2 重载函数
重载函数也是被支持的,但是必须记住,两个名字相同的函数的区别是基于其映射到lua中的参数类型。所以,尽管:
void func (int a);
void func (double a);
这两个函数在C++中是不同的,但是他们对于lua来说是相同的函数,因为int和double映射到lua中的函数都是number类型。
还有另外一种特殊的情况,当所有的参数都是指针时,如:
void func (char* s);
void func (void* p);
void func (Object1* ptr);
void func (Object2* prt);
上面四个函数在C++中时不同的,但是在lua中用一个函数都可以匹配他们:
func(nil)
所以知道tolua是如何解决在运行时调用那个函数时非常重要的,其方法就是就是尝试匹配没一个提供的函数,首先匹配最后一个被指定的函数,假如失败了,继续匹配前一个被指定的函数,重复这过程,直到有函数匹配或者匹配到第一个被指定函数。如果匹配到第一个被指定函数都还没有匹配成功,那么将会报告“mismatching error” 消息。但性能很重要时,我们可以将最常用的函数放在最后一个指定,因为它将第一个被匹配。
Tolua 运行在C中使用重载,细节请看“重命名“章节。
8.3 默认参数值
函数的最后几个参数可以指定默认值,这样,在函数被调用时有默认值的参数可以不传入值,而是从默认值中获取。在pkg文件总指定默认值的格式和C++中一样。
type funcname (..., typeN-1 parN-1 [= valueN-1], typeN parN [= valueN]);
tolua 实现这个功能没有使用任何的C++机制,所以它也可以用于绑定C函数。
我们也可以为数组的元素指定默认值(尽管没有方法为一个数组指定默认值),例:
void func (int a[5]=0);
上面代码指定数组元素的默认值是0,所以这个函数在lua中可以使用一个没有被初始化的表来调用。
对于Lua object 类型(lua_object), tolua 定义了一个一个常理,它指定为nil作为一个默认值。
void func (lua_Object lo = TOLUA_NIL)
tolua++扩展:
C++构造器作为默认参数是有效的,例:
void set_color(const Color& color = Color(0,0,0));
8.4 多返回值
在lua中,一个函数可以返回任意多个值,tolua使用这个功能通过引用模拟值传
递。假如一个函数的参数使用指针或者引用作为参数类型,tolua将支持对应的类型作为输入并且返回对应的类型。
例:
void swap (double* x, double* y);
or
void swap (double& x, double& y);
假如这个函数被声明在pkg文件中,tolua 将会以一个有两个输入参数,两个返回值的函数绑定它。所以在lua中正确的调用:
x,y = swap(x,y)
使用默认值的情况:
void getBox (double* xmin=0, double* xmax=0, double* ymin=0, double* ymax=0);
In Lua:
xmin, xmax, ymin, ymax = getBox()
使用用户类型的情况:
void update (Point** p);
or
void update (Point*& p);
9 绑定结构体域
Tolua完美的支持用户定义类型绑定,对于不是基础类型的变量类型或者函数类型,tolua会自动创建一个标记的userdata来当作这个类型。假如类型是一个结构体,这个结构体的域可以直接在lua中访问,在C代码中,类型定义通用typedef:
typedef struct [name] {
type1 fieldname1;
type2 fieldname2;
...
typeN fieldnameN;
} typename;
假如上面的代码被绑定到lua中,我们可以这样访问,假如var是上面类型的一个变量或者对象,我们可以通过var.fieldnamei 来访问类型中域名为fieldnamei的值。
通用支持域是数组的情况:
typedef struct {
int x[10];
int y[10];
} Example;
10 绑定类和方法
Tolua支持C++中类的定义,实际上,tolua使用了一个自然的方法处理单继承和多状态的。接下来的部分描述了类中那些能够被导入到lua中。
10.1 指定继承
假如var是一个lua变量,var保存了一个派生类对象,var可以用于任何其基类的地方,并且var可以访问其基类的任何方法。为了这一机制能够生效,我们必须指明派生类继承的基类。使用传统的方法,如下:
class classname : public basename
{
/* class definition */
};
如在lua中要使用继承的属性, 那么基类basename要在派生类classname前定义。
10.2 多继承
Tolua++从版本1.0.4开始支持多继承,允许你手动的访问其他的父类。
例:
class Slider : public Widget, public Range {
...
};
一个Slider的对象将会完全的继承widget,以及会包含一个Range类型的成员,例:
For example:
slider = Slider:new()
slider:show() -- a Widget method
slider:set_range(0, 100) -- this won't work, because
-- set_range is a method from Range
slider.__Range__:set_range(0, 100) -- this is the correct way
这只是一个实验功能。
10.3 指定导出成员和方法
对于结构体域,类域,静态的或者非静态的都可以被导出。类的方法和类静态方法也可以别导出。当然他们在C++中必须是被定义为public的(这public: 关键之可以保留在pkg文件中,它将会被tolua忽略)。
每一个绑定的类,tolua创建一个lua表来存储它,并且命令和C++类名一样。这个表当中可能包含其他表,表示这个类中可能包含其他类或者结构体。静态的方法通过表可以直接调用,非静态的必须通过对象访问,即一个包含对象的变量。
Tolua支持一些特殊的方法。构造器被定义为静态的,名字为new,new_local
(tolua++), 或者直接调用类名(具体的细节见下文),析构函数被定义为delete方法。
注意tolua支持重载,这个可以应用于构造函数,同样注意virtual关键字在pkgfile中无效。
下面是一个能够被tolua解析的pkgfile,例:
class Point {
static int n; // represents the total number of created Points
static int get_n(); // static method
double x; // represents the x coordinate
double y; // represents the y coordinate
static char* className (void); // returns the name of the class
Point (void); // constructor 1
Point (double px, double py); // constructor 2
~Point (void); // destructor
Point add (Point& other); // add points, returning another one
};
class ColorPoint : public Color {
int red; // red color component [0 - 255]
int green; // green color component [0 - 255]
int blue; // blue color component [0 - 255]
ColorPoint (double px, double py, int r, int g, int b);
};
导入lua后,我们可以用以下的例子测试:
p1 = Point:new(0.0,1.0)
p2 = ColorPoint:new(1.5,2.2,0,0,255)
print(Point.n) -- would print 2
print(Point:get_n()) -- would also print 2
p3 = p1:add(p2)
local p4 = ColorPoint()
print(p3.x,p3.y) -- would print 1.5 and 3.2
print(p2.red,p2.green,p2.blue) -- would print 0, 0, and 255
p1:delete() -- call destructor
p2:delete() -- call destructor
注意:我们仅仅能够delete我们自己创建的对象,p3将会被自动回收,我们不能够delete它。
Tolua++扩展:需要注意p4是直接调用类名创建的,这种方式和调用new_local方法功能是一样的,当不适用这个对象时,这个对象可以被垃圾回收器自动回收。所以它也不应该手动delete。对于在pkg文件中声明的构造函数,在lua中这个类都会有三个方法:new,new_local,.call回调与之对应。
当然我们仅仅需要指定那些我们需要导入到lua中的方法和成员。但是有时候我们也需要声明一个没有任何方法和成员的类,这样做的目的是不打断继承链。
10.4 使用正规函数做为类的方法
从tolua++ 1.0.5开始,可以使用一个tolua_outside关键字指定一个正规函数作为一个类的方法或者静态方法,例:
/// position.h:
typedef struct {
int x;
int y;
} Position;
Position* position_create();
void position_set(Position* pos, int x, int y);
/// position.pkg:
struct Position {
int x;
int y;
static tolua_outside Position* position_create @ create();
tolua_outside void position_set @ set(int x, int y);
};
--------------- position.lua
local pos = Position:create()
pos:set(10, 10)
注意,在position_set方法中调用了一个position * 作为其第一个参数,这个参数在使用tolua_outside声明时将会被省略。
注意,我们不能命名为new或者new_local方法,以及不能作为重载操作符,否则将会返回行为未定义。
10.5 重载操作符
Tolua自动绑定下面的二元操作符:
operator+ operator- operator* operator/
operator< operator>= operator== operator[]
对于这些关联操作符,tolua自动将返回的0值转换成nil,所以在C中的false将转变成lua中的false。
用上面的代码举一个例子,替换它的一个行为:
Point add (Point& other); // add points, returning another one
we had:
Point operator+ (Point& other); // add points, returning another one
这样在lua中我们就能简写成:
p3 = p1 + p2
索引操作符(operator[])在只有一个数字参数时也可以导入到乱中。这种情况下,tolua支持返回一个引用或者一个基本类型。假如返回一个引用,在lua中,程序员即可以获取也可以设置它的值。假如返回一个非引用,程序员则只能获取它的值。举一个例子来说明,假如我们有一个vector的类,以及绑定了下面的操作符:
double& operator[] (int index);
在这种情况下,在lua中,我们可以这样使用:
value = myVector[i] and myVector[i] = value
假如我们按如下方式绑定操作符:
在lua中我就只能这样使用:
value = myVector[i]
释放函数(不是class成员)的函数重载不支持。
10.6 转换操作符
Tolua++扩展:从版本1.0.90开始支持转换操作符,例:
/// node.h
// a class that holds a value that can be of type int, double, string or Object*
class Node { // tolua_export
private:
union {
int int_value;
double double_value;
string string_value;
Object* object_value;
};
// tolua_begin
public:
enum Type {
T_INT,
T_DOUBLE,
T_STRING,
T_OBJECT,
T_MAX,
};
Type get_type();
operator int();
operator double();
operator string();
operator Object*();
};
// tolua_end
Tolua++将会生成转换对象Node调用操作符的代码(使用C++ static_cast),并把在类中以“.typename”形式注册他们。例:
-- node.lua
local node = list.get_node("some_node") -- returns a Node object
if node.get_type() == Node.T_STRING then
print("node is "..node[".string"]())
elseif node.get_type() == Node.T_OBJECT then
local object = node[".Object*"]()
object:method()
end
11 绑定属性
Tolua++从1.0.6版本开始支持声明类属性,使用关键字tolua_property, 一个看起来像域的属性,但是其值是同调用类方法获取的。例:
/// label.h
class Label {
public:
string get_name();
void set_name(string p_name);
Widget* get_parent();
};
/// label.pkg
class Label {
tolua_property string name;
tolua_readonly tolua_property Widget* parent;
};
--------------- label.lua
local label = Label()
label.name = "hello"
print(label.name)
label.parent:show()
11.1 属性类型
一个属性可以有不同的类型,这些类型决定了如何设置和获取它的值。Tolua++提供了3中不同的嵌入类型。
Default类型,默认情况下,访问用名字访问这个属性时将会调用其get_name 和set_name方法。
qt 类型将会使用name和setNmae方法。
overload 类型,将会都用name方法,只不过这个方法被重载了两个函数,获取函数为:'string name(void); 设置函数为:'void name(string);
这个属性的类型可以加在tolua_property关键字的后面,例:
tolua_property__qt string name;
当没有类型指定时,默认使用default类型,但是这是可以被改变的(见下文)
11.2 改变默认属性类型
这默认的属性类型可以使用宏'TOLUA_PROPERTY_TYPE'改变,这个宏将会在其调用为止改变属性类型,直到遇到宏所在位置的块结束符。例:
TOLUA_PROPERTY_TYPE(default); // default type for the 'global' scope
namespace GUI {
class Point {
tolua_property int x; // will use get_x/set_x
tolua_property int y; // will use get_y/set_y
};
TOLUA_PROPERTY_TYPE(qt); // changes default type to 'qt' for the rest of the 'GUI' namespace
class Label {
tolua_property string name; // will use name/setName
};
};
class Sprite {
tolua_property GUI::Point position; // will use get_position/set_position
tolua_property__overload string name; // will use name/name
};
11.3 增加自定义属性类型
自定义属性类型可以通过重定义函数get_property_methods_hook(细节见自定义tolua++章节)来增加。这个函数接受属性类型和名字,并且返回setter 和 getter函数名字。例:
/// custom.lua
function get_property_methods_hook(ptype, name)
if ptype == "hungarian_string" then
return "sGet"..name, "Set"..name
end
if ptype == "hungarian_int" then
return "iGet"..name, "Set"..name
end
-- etc
end
/// label.pkg
class Label {
tolua_property__hungarian_string string Name; // uses 'sGetName' and 'SetName'
tolua_property__hungarian_int string Type; // uses 'iGetType' and 'SetType'
};
12 模板类
Tolua++新增功能中支持模板类,使用指令TOLUA_TEMPLATE_BIND,例:
class vector {
TOLUA_TEMPLATE_BIND(T, int, string, Vector3D, double)
void clear();
int size() const;
const T& operator[](int index) const;
T& operator[](int index);
void push_back(T val);
vector();
~vector();
};
这个TOLUA_TEMPLATE_BIND指令,必须在类声明的一开始就使用,否则它将会被忽略。上面代码将会创建4个vector类。每一个在TOLUA_TEMPLATE_BIND参数中指定的类型都将会体会宏T(在TOLUA_TEMPLATE_BIND中的第一个参数)。因此,函数operator[], &operator[] 和 push_back 将会有不同的标识在不同的对象版本中。在C++中一个对象将会使用vector<type> 声明,在lua中对应表名是vector_type_。所以lua中的代码可以这样写:
string_vector = vector_string_:new_local()
string_vector:push_back("hello")
string_vector:push_back("world")
print(string_vector[0].." "..string_vector[1])
同样的,一个模板类可能有多个宏,同时它也可能是从其他模板类继承而来,例:
class hash_map : public map<K,V> {
TOLUA_TEMPLATE_BIND(K V, int string, string vector<double>)
V get_element(K key);
void set_element(K key, V value);
hash_map();
~hash_map();
};
在这个例子中,一个对象有其他模板作为其类型,所以它的声明为:hash_map<string,vector<double>>, 然而它在lua中对应的表为:hash_map_string_vector_double___(在类型重命名章节中有一个更好的方法访问这些对象)。
注意,由于定义某些模板类的复杂性,你应该关心如何声明它们。例如,你创建了类型(hash_map<string,vector<double> >)创建了一个对象,然后适用这个类型(hash_map<string, vector<double> >)声明一个变量(注意这个类型在string和vector之间多了一个空格),这个变量的类型无法认证的,无效的。比较好的方法是声明一个typedef,并且每一个类型都适用它(这是C语言的常用做法)。我们用上面的vector举个例子:
typedef vector VectorInt;
VectorInt variable;