在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
<<Programing In Lua>>中学习了闭包,元表和lua的面向对象实现后,我被的元机制震撼了,果断体会到了如果把自己禁锢在C++的圈子里坐井观天是多么的可惜. 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
(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对象.
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
|
请发表评论