当在c++的工程使用lua时,可以通过tolua++将c++的类注册到lua中,从而使得可以在lua中使用c++中的类和对象。先不说其主要可以做些什么,当了解到更多时再进行解说。现在这里仅仅说明toua++初始化时的动作。
假设当前有两个类:CMyObject和CStudent,CStudent继承CMyObject,如下:
CMyObject:
class CMyObject { public: CMyObject(); ~CMyObject(); };
CStudent:
class CStudent:public CMyObject { public: CStudent(void); ~CStudent(void); void Run(); void Run(int a); };
编写的pkg文件如下:
Student.pkg:
$#include "MyObject.h" $#include "Student.h" class CMyObject { public: CMyObject(); ~CMyObject(); } class CStudent:public CMyObject { public: CStudent(); ~CStudent(); void Run(); void Run @ Run2(int a); };
使用tolua++.exe通过命令生成lua_CStudent.cpp文件,其中就是通过tolua++导入lua中的函数。其中初始化函数为tolua_Student_open(lua_State* tolua_S),一般会在main函数中,在luaL_newstate创建了一个新的lua状态指针后调用,来将c++中的类注册到lua环境中接着就可以直接执行lua文件。现将其中的主要步骤说明如下:
特殊符号的说明:table1-->table2表示的为table2为table1的元表;
1、tolua_open:其中,首先判断当前的环境是否已经被打开,如果未打开,则会在lua注册表(lua中,注册表是一个特殊的table,可以通过伪索引LUA_REGISTRYINDEX来进行操作)中写入一个键值对reg["tolua_opened"=true]。接下来是对一些公用的table进行初始化,包括,但不限于reg["tolua_ubox"=table-->table["__mode"="v"]]、reg["tolua_super"=table]、reg["tolua_gc"=table]、reg["tolua_commonclass"=table]等,接下来会在全局表_G(后面使用global代替)中,创建一个key为"tolua"的table,并在其中写入C++函数,如下:global["tolua"]["type"=tolua_bnd_type]、global["tolua"]["takeownership"=tolua_bnd_takeownership]、global["tolua"]["releaseownership"=tolua_bnd_releaseownership]、global["tolua"]["cast"=tolua_bnd_cast]、global["tolua"][“inherit”=tolua_bnd_inherit]、global["tolua"]["setpeer“=tolua_bnd_setpeer]、global["tolua"]["getpeer"=tolua_bnd_getpeer]。这些函数,暂时跟踪代码并没有发现有用到的地方。
2、tolua_reg_types:从命令可以看出来是注册类型,其中就是将CMyObject和CStudent注册到lua环境中。这里的注册比较简单,就是在lua注册表中创建两个键值对,其中键就是两个类名称,而值为两个table,即reg["CMyObejct"=table]和reg["CStudent"=table]。其中使用的是lua提供的函数来进行注册:luaL_newmetatable,这个是辅助库中的函数。lua5.1的参考文档中说,必须用此函数来创建的table才可以作为其他table的元表。其实,也就是在lua注册表中创建了table,然后将这个table又插入到当前的栈中。创建了两个table后,又在这两个table中写入了写元表的默认操作,拿reg["CStudent"]来说,即有如下操作:
reg[“CStudent”][“__index”=class_index_event, “__newindex”=class_newindex_event, “__add”=class_add_event, “__sub”=”class_sub_event”, “__mul”=class_mul_event, “__div”=class_div_event, “__lt”=class_lt_event, “__le”=class_le_event, “__call”=class_call_event],从这里可以看出来,这个表在后面肯定会作为其他对象的元表,更大胆的猜测就是作为创建的CStudent的c++对象的元表,因为把c++对象注册到lua环境中时,使用的是userdata类型,其在lua环境中没有任何的默认操作。
接着设置操作了一次tolua_super对应的table,是设置当前类的基类。例如CStudent的基类为CMyObject,则在reg["tolua_super"]中有reg["tolua_super"][reg["CStudent"]=table["CMyObject"=true]]。假如CStudent再有一个基类CTest的话,则为reg["tolua_super"][reg["CStudent"]=table["CMyObject"=true, "CTest"=true]],这样可以查询到当前类的基类。
3、tolua_cclass(tolua_S,"CStudent","CStudent","CMyObject",tolua_collect_CStudent):这个函数中主要动作有两个 1)将基类作为继承类的元表,接着在reg["CStudent"]中生成一个新的tolua_ubox的table,即reg["CStudent"]["tolua_ubox"=table],这个table会在后面用到。 2)设置继承同2中的reg["tolua_super"]的操作。所以从这里可以看到有些重复,但是具体为何这么做,是因为代码是自动生成的,所以不会那么严谨还是其他原因?暂时没有去深究。
4、将c++中类的成员函数注册到lua中。例如注册CStudent中的Run函数,这是在reg["CStudent"]中插入一对键值对,键为函数名称,值为tolua++为CStudent的Run函数生成的一个外壳函数,名称为“tolua_Student_CStudent_Run00”(这个函数在使用tolua++.exe命令生成C++外壳时,tolua++给Run函数生成的外壳函数),执行操作reg["CStudent"]["Run"=tolua_Student_CStudent_Run00],其他需要暴露给lua的函数也都执行类似操作即可。此时,所有需要注册到lua中的函数已经全部完成。
5、在lua中创建CStudent的对象,由于tolua++会自动为CStudent生成一个new的外壳函数,用来创建c++的对象。在中间会使用new创建一个CStudent的对象,然后将此对象作为userdata压入到lua栈中,接着将reg["CStudent"]作为userdata的元表。有两点需要理解:1)tulua_ubox:在使用tolua++的tolua_pushusertype中,首先会查找当前类在lua中对应的table,即上面注册进lua的reg["CStudent"]中的"tolua_ubox"对应的table,即reg["CStudent"]["tolua_ubox"] ,这里在第3步中有说到。假如这个table不存在,会去查找reg["tolua_ubox"]。然后再reg["CStudent"]["tolua_ubox"]中查找当前的userdata是否已经在其中,如果在的话,就不进行新的注册;否在,将新的userdata压入其中。在这里压入userdata时使用了一个lightuserdata和userdata的键值对。在压入reg["CStudent"]["tolua_ubox"]的同时,在栈空间同样留了一个索引。然后再讲reg["CStudent"]设置为新的userdata的元表。 2)如何将userdata关联到相应的函数的:这里tolua++做了一个处理,在将类CStudent注册进lua时,在reg["CStudent"]中压入了一个函数reg["CStudent"]["__index"=class_index_event](在2中有提到)。这里简单说明下元表中的__index域,这是一个特殊的域,在lua查找一个对象的域的时候,当查找不到时,会去查看它的元表,当有元表时,就会调用元表中的__index域。tolua++中正是用了这个原理,当在lua环境中查找CSudent对象的函数,由于在lua环境中,C++对象是一个userdata,因此,会去查看其元表。而它的元表为reg["CStudent"],其中有__index域。__index域在tolua++中对应的为一个函数:class_index_event,为tolua++自定义的函数。在这个函数中会根据传入的参数在reg["CStudent"]中查找对应的函数。例如调用CStudent中Run成员函数,则在class_index_event会出入两个参数,一个位userdata,另外一个则为Run字符串。这样就可以在userdata的元表reg["CStudent"]中根据字符串Run查找到对应的函数,然后进行调用。
暂时理解的tolua++的过程即为此,后面当理解深的时候,还会对本文进行修改。如有不对的地方,欢迎网友指出,谢谢。