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_begin
和tolua_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
,float
和double
类型被映射为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支持两种绑定常量的方式:define
和enum
。
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个类,Foo
和Bar
,使用-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() ... |