<<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 LuaTest 12 { 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 Lua 26 #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 1 100 // get member function from upvalue 101 RegType *l = static_cast<RegType*>(lua_touserdata(L, lua_upvalueindex(1))); 102 return (obj->*(l->mfunc))(L); // call member function 103 } 104 105 // create a new T object and 106 // push onto the Lua stack a userdata containing a pointer to T object 107 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 objects 110 userdataType *ud = 111 static_cast<userdataType*>(lua_newuserdata(L, sizeof(userdataType))); 112 ud->pT = obj; // store pointer to object in userdata 113 luaL_getmetatable(L, T::className); // lookup metatable in Lua registry 114 lua_setmetatable(L, -2); 115 return 1; // userdata containing pointer to T object 116 } 117 118 // garbage collection metamethod 119 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 objects 123 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 registry 10 lua_setmetatable(m_pLuaState, -2); 11 12 //拷貝棧頂userdata 13 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 userdata 11 luaL_getmetatable(m_pLuaState, T::className); // lookup metatable in Lua registry 12 lua_setmetatable(m_pLuaState, userdata); 13 14 //獲取對象table 15 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
這里也貼上這兩天無所事事收集到的資源鏈接:
<<游戲編程精粹>>5,6中都有關於lua與腳本支持的相關文章.
http://blog.csdn.net/passers_b/article/details/7773547
http://blog.csdn.net/kesalin/article/details/2556553
http://kasicass.blog.163.com/blog/static/39561920084394247558/
http://www.cppblog.com/kevinlynx/archive/2011/04/24/144905.html
https://github.com/vinniefalco/LuaBridge
http://www.cnblogs.com/ringofthec/archive/2010/10/26/luabindobj.html
http://www.cnblogs.com/sniperHW/archive/2012/04/20/2460643.html
http://blog.monkeypotion.net/gameprog/beginner/introduction-of-scripting-system-and-lua