tolua++中文参考手册【完整翻译】

by Waldemar Celes, Ariel Manzur.

tolua++中文参考手册【完整翻译】

官网:http://www.codenix.com/~tolua/tolua++.html
翻 译: ChildhoodAndy
BLOG: childhood.logdown.com
EMAIL:dabing1022@gmail.com

译者前言:
由于工作接触quick-cocos2d-x的缘故,接触到了luabinding。对luabinding的理解在一定程度上会加深对quick框架的理解(也包括cocos2dx的lua绑定),在学习的过程中便涉及到了tolua++。先到官网上大概看了下,有个参考手册,谷歌百度之,现在网络存在的版本有两种,一种是谷歌机器人直接翻译,非常生涩,一种是其他朋友自己翻译的,但不完整,其中的翻译也不是很理想,所以萌生了自己翻译的这么一个想法。我不能保证这个完整版没有错误,但会尽我最大的努力去还原作者的本意。如果读者发现部分的翻译有问题,非常欢迎在我的博客留言或者给我发送email邮件,我会认真阅读,如果正确我将会更新翻译,以便呈现在大家面前的是一个准确描述的译本。


tolua++是tolua的扩展版本,是一款能够集成C/C++与lua代码的工具。在面向C++方面,tolua++包含了一些新的特性比如:

  • 支持std::string作为基本类型(这个可以由一个命令行选项关闭)
  • 支持类模板

以及其他的特性还有一些bug的修复。

tolua这款工具,极大的简化了C/C++代码与lua代码的集成。基于一个干净的头文件(或者从实际头文件中提取), tolua会自动生成从lua访问C/C++功能的绑定代码。

##tolua如何工作

要使用tolua,我们需要创建一个package文件(译者注:pkg文件),即一个从C/C++实际头文件整理后的头文件,列举出我们想导出到lua环境中的那些常量、变量、函数、类以及方法。然后tolua会解析该文件并且创建自动绑定C/C++代码到lua的C/C++文件。如果将创建的文件同我们的应用链接起来,我们就可以从lua中访问指定的C/C++代码。

我们从一些例子开始。如果我们指定下面的类似C头文件作为输入给tolua:

1
#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);

就会自动创建一个绑定上面代码到lua的C文件。因此,在lua代码里, 我们可以访问C代码。举个例子:

1
...
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++头文件

1
#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++文件,从而为我们提供lua层访问C++层所需要的对应的代码。因此,以下的的lua代码是有效的:

1
...
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的package文件并不是真正的C/C++头文件,而是清理过的版本。tolua并没有实现对C/C++代码的完全解析,但是却能够解析暴露给lua的功能的声明。通常头文件可以被包括进package文件里,tolua将会提取出用户指定的代码以用于解析头文件。

##如何使用tolua

tolua由两部分代码组成:可执行程序和静态库(an executable and a library)。可执行程序用于解析,读入package文件,然后输出C/C++代码,该代码提供了从lua层访问C/C++层的绑定。如果package文件是与C++类似的代码(例如包括类的定义),就会生成一份C++代码。如果package文件是与C类似的代码(例如不包括类),就会生成一份C代码。tolua可接受一些选项。运行tolua -h显示当前可接受的选项。例如,要解析一个名为myfile.pkg生成一个名为myfile.c的绑定代码,我们需要输入:

tolua -o myfile.c myfile.pkg

产生的代码必须被编译并且和应用程序进行链接,才能提供给Lua进行访问。每个被解析的文件代表导出到lua的package。默认情况下,package的名称就是是输入文件的根名称(例子中为myfile),用户可以指定一个不同的名称给package:

tolua -n pkgname -o myfile.c myfile.pkg

package还应当被明确初始化。为了从C/C++代码中初始化package,我们需要声明和调用初始化函数。初始化函数被定义为:

int tolua_pkgname_open (lua_State*);

其中pkgname是被绑定package的名字。如果我们使用的是C++,我们可以选择自动初始化:

tolua -a -n pkgname -o myfile.c myfile.pkg

在这种情况下,初始化函数会被自动调用。然而,如果我们计划使用多个Lua State,自动初始化就行不通了,因为静态变量初始化的顺序在C++里没有定义。

tolua生成的绑定代码使用了一系列tolua库里面的函数。因此,这个库同样需要被链接到应用程序中。tolua.h也是有必须要编译生成的代码。

应用程序无需绑定任何package文件也可以使用tolua的面向对象框架。在这种情况下,应用程序必须调用tolua初始化函数(此函数被任何package文件初始化函数调用):

int tolua_open (void);

##基本概念

使用tolua的第一步就是创建package文件。我们从真正的头文件入手,将想要暴露给lua的特性转换成tolua可以理解的格式。tolua能够理解的格式就是一些简单的C/C++声明。我们从下面几个方面来讨论:

####文件包含

一个package文件可以包含另外的package文件。一般格式是:

$pfile "include_file"

一个package文件也可以包含常规的C/C++头文件,使用hfile或者cfile命令:

$cfile "example.h"

在这种情况下,tolua将会提取出被tolua_begintolua_end所封闭的代码,或者tolua_export所在的单行。以下面C++代码为例:

1
#ifndef EXAMPLE_H
#define EXAMPLE_H

class 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函数被留在了package文件之外。

最后,lua文件可以被包含进package文件中,使用$lfile:

$lfile "example.lua"

tolua++新特性:从tolua++1.0.4版本以后,还有一种方式来包含源文件就是用ifile:

$ifile "filename"

ifile允许在文件名之后附带额外的可选的参数,举个例子:

1
$ifile "widget.h", GUI
$ifile "vector.h", math, 3d

ifile默认会将整个文件原封不动的包含进去,但是文件的内容和额外的参数通过include_file_hook函数才能被包含进package文件中。

####基本类型

tolua会自动映射C/C++的基本类型到lua的基本类型。

  • char,int,floatdouble类型被映射为lua中的number
  • char*被映射为lua中的string
  • void*被映射为lua中的userdata

C/C++中的数据类型前面可能有修饰语(如unsigned, static, short, const等等)。然而我们要注意到tolua会忽略基本类型前面的修饰语const。因此,我们给lua传递一个基本类型常量然后再从lua中传递回给C/C++代码,常量到非常量的转换会被悄悄的完成。

C/C++函数也可以对lua对象进行明确的操作。lua_Object被认为是一个基本类型,任何lua值都符合。

tolua++新特性 :C++中的string类型同样被认为是基本类型,会被当作值传递给lua(使用c_str()方法)。这个功能可以使用命令行选项-S进行关闭。

####用户自定义类型

在package文件里的所有其他类型都会被认为是用户自定义类型。它们会映射到lua的userdata类型。lua只能存储指向用户自定义类型的指针;但是,tolua会自动采取措施来处理引用和值。例如,如果一个函数或方法返回一个用户定义类型的值,当这个值返回给lua的时候,tolua会分配一个克隆对象,同时会设置垃圾收集标记,以便在lua不再使用该对象时会自动释放。

对于用户定义类型,常量是被保留的,因此将用户自定义类型的非常量作为常量传递给一个函数时,会产生类型不匹配的错误。

####NULL和nil

C/C++的NULL或0指针映射到lua中的nil类型。反之,nil却可以被指定为任何C/C++指针类型。这对任何类型都有效:char*, void*以及用户自定义类型指针。

####Typedefs

#####真实头文件的包含

##绑定常量

tolua支持两种绑定常量的方式:defineenum

  • define通常的格式是:#define NAME [ VALUE ]

上面的VALUE是可选的。如果这样的代码出现在被解析的文件中,tolua会将NAME作为lua的全局变量,该全局变量是C/C++的常量,值为VALUE。这里只接受数字常量。

tolua++新特性:所有的其他预处理指令会被忽略。

  • enum的一般格式:
1
enum {
  NAME1 [ = VALUE1 ] ,
  NAME2 [ = VALUE2 ] ,
  ...
  NAMEn [ = VALUEn ]
};

同样的,tolua会创建一系列全局变量,命名为NAMEi,对应着各自的值。

##绑定外部变量

##tolua++的额外特性

####多个变量声明
同一类型的多个变量可以一次性声明,如:

float x,y,z;

这将会创建3个不同的浮点型变量。确保逗号之间没有任何空格,否则会解析错误。

####tolua_readonly
任何变量声明都可以使用tolua_readonly修饰符来确保变量只读,即使该变量为非常量类型。例如:

1
class Widget {

	tolua_readonly string name;
};

这个特性可以用来’hack’一些不支持的东西如operator->。考虑下面这个pkg文件:

1
$hfile "node.h"
$#define __operator_arrow operator->()
$#define __get_name get_name()

node.h文件内容如下:

1
template class<T>
class Node { // tolua_export

private:
	string name;
	T* value;

public:

	T* operator->() {return value;};
	string get_name() {return name;};

	// tolua_begin

	#if 0
	TOLUA_TEMPLATE_BIND(T, Vector3D)

	tolua_readonly __operator_arrow @ p;
	tolua_readonly __get_name @ name;
	#endif


	Node* next;
	Node* prev;

	void set_name(string p_name) {name = p_name;};

	Node();
};
// tolua_end

虽然这样对头文件的处理不是很漂亮,但是却解决了下面几个问题:

  • operator->()方法可以通过在lua中使用对象的变量p来调用
  • get_name()方法可以通过在lua中使用对象的变量name来调用

lua用法举例:

1
node = Node_Vector3D_:new_local()
-- do something with the node here --
print("node name is "..node.name)
print("node value is ".. node.p.x ..", ".. node.p.y ..", ".. node.p.z)

tolua++会忽略掉所有的预处理命令(除了#define), 但node.h仍然是一个有效的C++头文件,同时也是tolua的一个有效的源,这样就避免了维护2个不同的文件,即使是具有不一样的功能的对象。

像变量一样去重命名函数的功能特性可能会被扩展进将来的版本中。

####在命令行中定义值

从版本1.0.92开始,命令行选项-E在tolua++运行的时候允许我们传值给luaState,就像GCC的-D一样。例如:

1
$ tolua++ -E VERSION=5.1 -E HAVE_ZLIB package.pkg > package_bind.cpp

上述命令将会在全局表_extra_parameters中添加两个字段,字符串值为“5.1”的“VERSION”,还有布尔值为true的“HAVE_ZLIB”。就目前而言,我们还不能使用这些值,除非用户自定义脚本。

####使用C++ typeid

从版本1.0.92开始,命令行选项-t变得可用。这将会产生一个对空宏Mtolua_typeid的调用的列表,该列表包含了C++ type_infoobject和区分其他类型的名字。例如,如果你的package文件中绑定了2个类,FooBar,使用-t会有如下输出:

1
#ifndef Mtolua_typeid
#define Mtolua_typeid(L,TI,T)
#endif
 Mtolua_typeid(tolua_S,typeid(Foo), "Foo");
 Mtolua_typeid(tolua_S,typeid(Bar), "Bar");

Mtolua_typename的实现留给读者作为练习。

##导出实用函数

tolua本身为lua导出了一些实用的函数,包括了面向对象的框架。tolua使用的package文件如下:

1
module tolua 
{ 
 char* tolua_bnd_type @ type (lua_Object lo); 
 void tolua_bnd_takeownership @ takeownership (lua_Object lo); 
 void tolua_bnd_releaseownership @ releaseownership (lua_Object lo); 
 lua_Object tolua_bnd_cast @ cast (lua_Object lo, char* type); 
 void tolua_bnd_inherit @ inherit (lua_Object table, lua_Object instance);

/* for lua 5.1 */ 
 void tolua_bnd_setpeer @ setpeer (lua_Object object, lua_Object peer_table); 
 void tolua_bnd_getpeer @ getpeer (lua_Object object); 
}

####tolua.type(var)

返回一个代表对象类型的字符串。例如,tolua.type(tolua)返回字符串table, tolua.type(tolua.type)返回cfunction。类似的,如果var是一个存储着用户自定义类型T的变量,tolua.type(var)将会返回const T或者T, 取决于它是否是一个常量引用。

#####tolua.takeownership(var)

接管对象引用变量的所有权,意味着当对象的所有引用都没有的话,对象本身将会被lua删除。

#####tolua.releaseownership(var)

释放对象引用变量的所有权。

#####tolua.cast(var, type)

改变var的元表以便使得它成为type类型。type须是一个完整的C类型对象(包括命名空间等)字符串。

#####tolua.inherit(table, var)

(tolua++新特性)tolua++会将table作为对象和var具有一样的类型,当必要的时候使用var。举个例子,考虑下这个方法:

1
void set_parent(Widget* p_parent);

一个lua对象可以这么使用:

1
local w = Widget()
local lua_widget = {}
tolua.inherit(lua_widget, w)

set_parent(lua_widget);

注意这只是使得table能够在必要的时候被识别为Widget类型。为了能够访问Widget的方法,你必须要实现你自己的对象系统。下面是个简单的例子:

1
lua_widget.show = Widget.show

lua_widget:show() 
-- 调用Widget的show方法
-- table作为self,因为 lua_widget继承于一个widget实例
-- tolua++会把self识别为Widget,并且调用方法

当然,一个更好的办法就是为lua对象增加__index元方法。

##嵌入Lua代码

tolua允许我们将lua代码嵌入到生成的C/C++代码中。要做到这一点,它对指定的lua代码进行编译,并且在生成的代码中产生一个C常量字符串,存储下来相应的字节码。当package文件被打开的时候,字符串被执行。嵌入的lua代码格式如下:

1
$[

嵌入的lua代码
...

$]

以下面的.pkg摘录为例:

1
/* Bind a Point class */ 
class Point 
{ 
 Point (int x, int y); 
 ~Point (); 
 void print (); 
 ... 
} CPoint;

$[

-- Create a Point constructor 
function Point (self) 
 local cobj = CPoint:new(self.x or 0, self.y or 0) 
 tolua.takeownership(cobj) 
 return cobj 
end

$]

绑定这样的代码之后,我们可以在lua中这么写:

1
p = Point{ x=2, y=3 } 
p:print() 
...
坚持原创技术分享,您的支持将鼓励我继续创作!