luna

http://bbs.luaer.cn/read.php?tid=258

https://github.com/Senscape/Dagon/blob/master/src/Luna.h

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值