lua綁定C++對象學習


<<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

http://www.fseraph.com/?p=412

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM