lua绑定C++对象学习

来源:互联网 发布:c语言计算符号函数 编辑:程序博客网 时间:2024/06/03 22:42

<<Programing In Lua>>中学习了闭包,元表和lua的面向对象实现后,我被的元机制震撼了,果断体会到了如果把自己禁锢在C++的圈子里坐井观天是多么的可惜.


来看看超轻量级对象绑定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)程序结束...  水落石出,源码面前,了无秘密啊.

 

这个例子lua构建的数据结构有这几个:

 

搞清楚了luna的对象绑定实现后,我自己稍微动手实现了C++对象的绑定到lua.因为上面说过了,luna只支持脚本中创建对象,而我想要的是在C++程序中创建对象后绑定到lua中.没研究过LuaBind等库的代码,思考了一下元表的利用,还是写出了这个功能.下面阐述下.

 

(1)绑定单个C++对象.所有代码与本文开头的一样,改动的是测试类的构造函数:

1 LuaTest::LuaTest()2 {3     SCRIPTNAMAGER.BindObjectToLua<LuaTest>("tester1", this);4 }
复制代码
 1 //绑定对象到lua全局表(userdata) 2         template<class T> 3         void    BindObjectToLua(const Ogre::String& nameInLua, T* pObject) 4         { 5             Luna<T>::userdataType *ud = static_cast<Luna<T>::userdataType*>(lua_newuserdata(m_pLuaState, sizeof(Luna<T>::userdataType))); 6             assert(ud); 7  8             ud->pT = pObject;  // store pointer to object in userdata 9             luaL_getmetatable(m_pLuaState, T::className);  // lookup metatable in Lua registry10             lua_setmetatable(m_pLuaState, -2);11 12             //拷贝栈顶userdata13             lua_pushvalue(m_pLuaState, -1);14             lua_setglobal(m_pLuaState, nameInLua.c_str());15         }
复制代码

lua脚本:

1 tester1:fun()

运行结果:

 代码就不解释了,有困难,找lua手册妥妥的 :D

 

(2)在脚本中以数组形式通过ID来索引对象.改动的地方如下:

复制代码
 1 class LuaTest 2 { 3 public: 4     LuaTest(const STRING& name, int objIndex); 5     LuaTest(lua_State* L) {} 6     int    fun(lua_State* L) { cout << m_name.c_str() << endl; return 0; } 7  8     static const char className[]; 9     static Luna<LuaTest>::RegType methods[];10 11     STRING    m_name;12 };
复制代码
1 LuaTest::LuaTest(const STRING& name, int objIndex)2 :m_name(name)3 {4     SCRIPTNAMAGER.BindObjectToLua<LuaTest>("ObjTable", objIndex, this);5 }
复制代码
 1 //绑定C++对象到lua的对象数组(table)中,如table Unit[0] = userdata0, Unit[1] = ... 2         template<class T> 3         void    BindObjectToLua(const STRING& tableName, int index, T* pObject) 4         { 5             Luna<T>::userdataType *ud = static_cast<Luna<T>::userdataType*>(lua_newuserdata(m_pLuaState, sizeof(Luna<T>::userdataType))); 6             assert(ud); 7  8             int userdata = lua_gettop(m_pLuaState); 9 10             ud->pT = pObject;  // store pointer to object in userdata11             luaL_getmetatable(m_pLuaState, T::className);  // lookup metatable in Lua registry12             lua_setmetatable(m_pLuaState, userdata);13 14             //获取对象table15             lua_getglobal(m_pLuaState, tableName.c_str());16             //没有则创建17             if(lua_istable(m_pLuaState, -1) == 0)18             {19                 lua_newtable(m_pLuaState);20                 lua_pushvalue(m_pLuaState, -1);21                 lua_setglobal(m_pLuaState, tableName.c_str());22             }23 24             int table = lua_gettop(m_pLuaState);25             //将userdata放入表中26             lua_pushnumber(m_pLuaState, index);27             lua_pushvalue(m_pLuaState, userdata);28             lua_settable(m_pLuaState, table);29         }
复制代码

main()中:

LuaTest t1("t1", 0);LuaTest t2("t2", 1);

lua脚本:

ObjTable[0]:fun()ObjTable[1]:fun()

运行结果:

同样就不解释了  :D

 

0 0
原创粉丝点击