lua绑定C++对象学习

来源:互联网 发布:民生证券mac版 编辑:程序博客网 时间:2024/06/05 14:13
来看看超轻量级对象绑定luna类的使用和实现吧.首先,它的使用比较简单(当然,我测试的就很简单,不考虑继承,类型检查等需要,毕竟,luna只是一个可供借鉴和学习的最基础实现而已),代码如下:
 1 extern "C" 2 { 3 #include <lua.h> 4 #include <lualib.h> 5 #include <lauxlib.h> 6 } 7 #include <stdio.h> 8 #include "luna.h" 9 10 11 class LuaTest12 {13 public:14     LuaTest()    {}15     LuaTest(lua_State* L)    {}16 17     static const char className[];18     static Luna<LuaTest>::RegType methods[];19 20 public:21     int    TestString(lua_State* L)    { printf("hello!\n"); return 0; }22 };23 24 const char LuaTest::className[] = "LuaTest";25 // Define the methods we will expose to Lua26 #define method(class, name) {#name, &class::name}27 Luna<LuaTest>::RegType LuaTest::methods[] =28 {29     method(LuaTest, TestString),30     {0,0}31 };32 33 int main()34 {35     lua_State* L = luaL_newstate();36     luaL_openlibs(L);37 38     Luna<LuaTest>::Register(L);39     luaL_dofile(L, "MyTest.lua");40 41     lua_close(L);42 43 44 45     return 0;46 }

MyTest.lua如下:

test = LuaTest()test:TestString()

运行结果:

 

可以看到,luna使用是比较MISS法则的,让我比较不习惯的是对象不是在宿主程序里创建的,而是在lua脚本里.大家都知道lua的C函数导出是很简单的,直接luaL_register()就行,那么对象导出是怎么实现的呢?打开luna.h,发现其不过百来行,关键是Luna类的Register这个方法.学习了lua创始人的书后,咱底气足足的,就来剖析一下它的实现吧:

 

  1 #ifndef _luna_h_  2 #define _luna_h_  3 /**  4  * Taken directly from http://lua-users.org/wiki  5  */  6   7 /* Lua */  8 extern "C" {  9 #include <lua.h> 10 #include <lauxlib.h> 11 #include <lualib.h> 12 } 13  14 template <typename T> class Luna { 15   typedef struct { T *pT; } userdataType; 16 public: 17   typedef int (T::*mfp)(lua_State *L); 18   typedef struct { const char *name; mfp mfunc; } RegType; 19  20   static void Register(lua_State *L) { 21     //新建一个table,methods保存其栈索引,这个方法表就是用来保存要导出的成员函数 22     lua_newtable(L); 23     int methods = lua_gettop(L); 24     //在注册表中新建一个元表,metatable保存其栈索引. 25     //元表是模拟面向对象机制的关键.后面将会把该元表赋予fulluserdata(C++对象在lua中的映射对象). 26     luaL_newmetatable(L, T::className); 27     int metatable = lua_gettop(L); 28      29     //全局表[T::className]=methods表 30     lua_pushstring(L, T::className); 31     lua_pushvalue(L, methods); 32     lua_settable(L, LUA_GLOBALSINDEX); 33  34     //设置metatable元表的__metatable元事件 35     //作用是将元表封装起来,防止外部的获取和修改 36     lua_pushliteral(L, "__metatable"); 37     lua_pushvalue(L, methods); 38     lua_settable(L, metatable);  // hide metatable from Lua getmetatable() 39  40     //设置metatable元表的__index元事件指向methods表 41     //该事件会在元表onwer被索引不存在成员时触发,这时就会去methods表中进行索引... 42     lua_pushliteral(L, "__index"); 43     lua_pushvalue(L, methods); 44     lua_settable(L, metatable); 45  46     //设置元表的__tostring和__gc元事件 47     //前者是为了支持print(MyObj)这样的用法.. 48     //后者是设置我们的lua对象被垃圾回收时的一个回调. 49     lua_pushliteral(L, "__tostring"); 50     lua_pushcfunction(L, tostring_T); 51     lua_settable(L, metatable); 52  53     lua_pushliteral(L, "__gc"); 54     lua_pushcfunction(L, gc_T); 55     lua_settable(L, metatable); 56  57     //下面一段代码干了这么些事: 58     //1.创建方法表的元表mt 59     //2.方法表.new = new_T 60     //3.设置mt的__call元事件.该事件会在lua执行到a()这样的函数调用形式时触发. 61     //这使得我们可以重写该事件使得能对table进行调用...如t() 62     lua_newtable(L);                // mt for method table 63     int mt = lua_gettop(L); 64     lua_pushliteral(L, "__call"); 65     lua_pushcfunction(L, new_T); 66     lua_pushliteral(L, "new"); 67     lua_pushvalue(L, -2);           // dup new_T function 68     lua_settable(L, methods);       // add new_T to method table 69     lua_settable(L, mt);            // mt.__call = new_T 70     lua_setmetatable(L, methods); 71  72     // fill method table with methods from class T 73     for (RegType *l = T::methods; l->name; l++) { 74       lua_pushstring(L, l->name); 75       lua_pushlightuserdata(L, (void*)l); 76       lua_pushcclosure(L, thunk, 1);        //创建闭包,附带数据为RegType项 77       lua_settable(L, methods);                //方法表[l->name]=闭包 78     } 79  80     //平衡栈 81     lua_pop(L, 2);  // drop metatable and method table 82   } 83  84   // get userdata from Lua stack and return pointer to T object 85   static T *check(lua_State *L, int narg) { 86     userdataType *ud = 87       static_cast<userdataType*>(luaL_checkudata(L, narg, T::className)); 88     if(!ud) luaL_typerror(L, narg, T::className); 89     return ud->pT;  // pointer to T object 90   } 91  92 private: 93   Luna();  // hide default constructor 94  95   // member function dispatcher 96   static int thunk(lua_State *L) { 97     // stack has userdata, followed by method args 98     T *obj = check(L, 1);  // get 'self', or if you prefer, 'this' 99     lua_remove(L, 1);  // remove self so member function args start at index 1100     // get member function from upvalue101     RegType *l = static_cast<RegType*>(lua_touserdata(L, lua_upvalueindex(1)));102     return (obj->*(l->mfunc))(L);  // call member function103   }104 105   // create a new T object and106   // push onto the Lua stack a userdata containing a pointer to T object107   static int new_T(lua_State *L) {108     lua_remove(L, 1);   // use classname:new(), instead of classname.new()109     T *obj = new T(L);  // call constructor for T objects110     userdataType *ud =111       static_cast<userdataType*>(lua_newuserdata(L, sizeof(userdataType)));112     ud->pT = obj;  // store pointer to object in userdata113     luaL_getmetatable(L, T::className);  // lookup metatable in Lua registry114     lua_setmetatable(L, -2);115     return 1;  // userdata containing pointer to T object116   }117 118   // garbage collection metamethod119   static int gc_T(lua_State *L) {120     userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1));121     T *obj = ud->pT;122     delete obj;  // call destructor for T objects123     return 0;124   }125 126   static int tostring_T (lua_State *L) {127     char buff[32];128     userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1));129     T *obj = ud->pT;130     sprintf(buff, "%p", obj);131     lua_pushfstring(L, "%s (%s)", T::className, buff);132     return 1;133   }134 };135 #endif


读了<<Programing In Lua>>后,这里的语义理解是木有问题的.现在只需走一遍程序执行流程,就能把一切串起来了.

(1)在宿主程序中,执行了这句:

Luna<LuaTest>::Register(L);

这是静态的准备工作,我们继续往下看

(2)开始执行脚本:

luaL_dofile(L, "MyTest.lua");

脚本第一句为:

test = LuaTest()

执行LuaTest(),这会导致什么发生呢?一切必有源头,查看Luna::Register()中的这几句:

1 //全局表[T::className]=methods表2     lua_pushstring(L, T::className);3     lua_pushvalue(L, methods);4     lua_settable(L, LUA_GLOBALSINDEX);

推出:LuaTest() => methods table() .而对一个表使用调用语法,导致其元表的__call事件被触发,于是 =>  new_T函数就被执行了...

 

(3)new_T()中,new了C++对象,并创建了lua中对应的映射对象(fulluserdata),然后从注册表中取出元表与lua对象绑定(可以看出,所有的对象是共享一个元表的).最后返回lua对象.

回过神来体会脚本这句代码 test = LuaTest(),竟然语如其意,同时构造了C++对象和lua对象.


(4)执行脚本第二句:

test:TestString()

这只是一个语法糖,等价于

test.TestString(test)

这导致test对象被索引"TestString"方法,当然它作为一个userdata是没有key的,故而触发其元表的__index事件,然后在methods table中搜索到了"TestString"项,最终调用其闭包thunk.

(5)执行到闭包thunk了.这里有一个传入参数,位于栈索引1,即test对象自身.先进行了类型检查check(),即检查test对象元表的名称是否等于T::className,不等则报错.这是为了检查出不正确的对象方法调用,如用A类对象去调用B类对象的方法..接下来几句代码妥妥的了,取出闭包附带数据RegType,调用C++对象成员函数TestString()...

(6)脚本执行结束,lua对象被垃圾回收,其__gc元事件被触发,gc_T()调用,C++对象被delete.

(7)程序结束...  水落石出,源码面前,了无秘密啊.

 

额外生成了这么一些数据结构,性能损失相对原生C++调用肯定是有的,具体是怎样的现在没必要去追究啦:

 

搞清楚了最简单的对象绑定实现后,可以选择看看更复杂的或者尝试在自己的作品中加入一点好玩的脚本.

<<游戏编程精粹>>5,6中都有关于lua与脚本支持的相关文章.