注:原文也在公司内部论坛上发了
概述
尽管将C++对象绑定到Lua已经有
tolua++(Cocos2d-x 3.0用的就是这个)、
LuaBridge(我们游戏client对这个库进行了改进)和
luabind等各种库能够直接使用了(lua-users.org上有对各种语言绑定到lua库的
汇总),但弄清楚C++对象绑定到Lua的常见方法,不但有助于更深的了解Lua的机制,还能够方便改动第三方库以满足实际项目需求。
本文通过分析第三方库Lunar(我们游戏服务端用的是Luna,Lunar是Luna添加版。但仍然足够简洁)的实现,来理解C++对象绑定到Lua的通常方法。Lunar的測试代码放在我的github上。
Lunar实现的功能以及原理
Lunar实现很简洁,同一时候实现了C++绑定到Lua主要功能。使用Lunar至少能够做到下面几点:
1、在脚本中,能够使用注冊过的类创建C++对象,此类C++对象,由Lua控制何时释放。
2、在C++创建的对象,能够压入栈中,供脚本使用这个对象,而且提供一个可选參数。来决定这个对象是由C++控制释放,还是Lua控制释放。
3、脚本中的C++类对象。能够调用注冊过的类成员函数。
4、在C++中,能够获取在脚本中创建的对象。而且在C++中能够调用这个对象的成员函数。
5、能够在脚本中定义对象的成员函数,而且能在C++中调用这些用脚本实现的成员函数。
在脚本中创建C++对象,实质返回给脚本是一个userdata类型的值ud。ud中保存一个指针,该指针指向所创建的C++对象。这时候Lua控制对象何时释放,即在Lua在回收userdata时,同一时候利用userdata的元表__gc字段来回收动态分配的对象, 这时候就不须要C++释放内存了。注意这里返回给脚本不能是lightuserdata。由于lightuserdata实质上仅仅是一个指针。不受垃圾回收收集器的管理,而且它也没有元表。
在脚本中创建的对象是由Lua来维护对象的生命周期。
在Lunar中还能够使用Lunar<T>::push(lua_State *L, T *obj, bool gc=false)向栈中压入对象供脚本使用,当中第三个參数能够决定创建的对象是由C++控制释放。还是Lua控制释放。 其原理是在把要传递给Lua的对象obj压入栈时,它首先利用对象地址在lookup表中查找(lookup是一个mode为"v"的weak table。保存全部在脚本中使用的对象。该表的key是对象地址。value是对象相应的userdata),若不在lookup中。则会创建一个新的userdata。并把它保存在lookup中,若第三个參数为false,即由C++控制对象释放。还会把上面的userdata保存在一个nottrash表中,nottrash是一个mode为"k"的weak
table,保存全部不会随userdata回收其对应对象也释放的userdata,该表key为userdata。value为true。
这样处理后,在Lua回收userdata时,首先检測userdata是否在nottrash中。若不在。则删除userdata所指向对象,否则须要C++自己释放所创建的对象。
在调用Lunar<T>::Register(lua_State *L)向脚本注冊类时,会创建两个表,一个是methods表。该表的key为函数名,value为函数地址(函数能够是C++中定义类的方法,也能够在Lua中定义函数),在Lua中调用的对象方法。都保存在该表中;还有一个是metatable表,做为userdata的元表,该metatable表保存了上面的lookup表和nottrash表。而且设置了metatable.__metatable = methods,这样在脚本中就隐藏了metatable表,也就是说在Lua中,调用getmetatable(userdata)得到的是methods表,而不是metatable表,在脚本中,给对象加入成员方法也是会保存在methods表中。
Lunar源代码分析
以下逐行分析了Luanr的实现,在附件中是Lunar的測试代码。例如以下:
006 |
#define
LUNAR_DECLARE_METHOD(Class, Name) {#Name, &Class::Name} |
008 |
template < typename T> class Lunar
{ |
012 |
typedef int (T::*mfp)(lua_State
*L); |
015 |
typedef struct { const char *name;
mfp mfunc; } RegType; |
018 |
static void Register(lua_State
*L) { |
023 |
int methods
= lua_gettop(L); |
026 |
luaL_newmetatable(L,
T::className); |
027 |
int metatable
= lua_gettop(L); |
031 |
lua_pushvalue(L,
methods); |
032 |
set(L,
LUA_GLOBALSINDEX, T::className); |
036 |
lua_pushvalue(L,
methods); |
037 |
set(L,
metatable, "__metatable" ); |
040 |
lua_pushvalue(L,
methods); |
041 |
set(L,
metatable, "__index" ); |
043 |
lua_pushcfunction(L,
tostring_T); |
044 |
set(L,
metatable, "__tostring" ); |
048 |
lua_pushcfunction(L,
gc_T); |
049 |
set(L,
metatable, "__gc" ); |
052 |
lua_pushcfunction(L,
new_T); |
053 |
lua_pushvalue(L,
-1); |
054 |
set(L,
methods, "new" ); |
055 |
set(L,
-3, "__call" ); |
056 |
lua_setmetatable(L,
methods); |
059 |
for (RegType
*l = T::methods; l->name; l++) { |
060 |
lua_pushstring(L,
l->name); |
061 |
lua_pushlightuserdata(L,
( void *)l); |
062 |
lua_pushcclosure(L,
thunk, 1); |
063 |
lua_settable(L,
methods); |
072 |
static int call(lua_State
*L, const char *method, |
073 |
int nargs=0, int nresults=LUA_MULTRET, int errfunc=0) |
075 |
int base
= lua_gettop(L) - nargs; |
076 |
if (!luaL_checkudata(L,
base, T::className)) { |
079 |
lua_settop(L,
base-1); |
080 |
lua_pushfstring(L, "not
a valid %s userdata" ,
T::className); |
084 |
lua_pushstring(L,
method); |
088 |
lua_gettable(L,
base); |
089 |
if (lua_isnil(L,
-1)) { |
090 |
lua_settop(L,
base-1); |
091 |
lua_pushfstring(L, "%s
missing method '%s'" ,
T::className, method); |
096 |
int status
= lua_pcall(L, 1+nargs, nresults, errfunc); |
098 |
const char *msg
= lua_tostring(L, -1); |
099 |
if (msg
== NULL) msg = "(error
with no message)" ; |
100 |
lua_pushfstring(L, "%s:%s
status = %d\n%s" , |
101 |
T::className,
method, status, msg); |
105 |
return lua_gettop(L)
- base + 1; |
111 |
static int push(lua_State
*L, T *obj, bool gc= false )
{ |
112 |
if (!obj)
{ lua_pushnil(L); return 0;
} |
113 |
luaL_getmetatable(L,
T::className); |
114 |
if (lua_isnil(L,
-1)) luaL_error(L, "%s
missing metatable" ,
T::className); |
115 |
int mt
= lua_gettop(L); |
119 |
subtable(L,
mt, "userdata" , "v" ); |
121 |
static_cast <userdataType*>(pushuserdata(L,
obj, sizeof (userdataType))); |
124 |
lua_pushvalue(L,
mt); |
125 |
lua_setmetatable(L,
-2); |
128 |
lua_checkstack(L,
3); |
132 |
subtable(L,
mt, "do
not trash" , "k" ); |
133 |
lua_pushvalue(L,
-2); |
134 |
lua_pushboolean(L,
1); |
145 |
static T
*check(lua_State *L, int narg)
{ |
147 |
static_cast <userdataType*>(luaL_checkudata(L,
narg, T::className)); |
149 |
luaL_typerror(L,
narg, T::className); |
157 |
typedef struct {
T *pT; } userdataType; |
162 |
static int thunk(lua_State
*L) { |
164 |
T
*obj = check(L, 1); |
167 |
RegType
*l = static_cast <RegType*>(lua_touserdata(L,
lua_upvalueindex(1))); |
168 |
return (obj->*(l->mfunc))(L); |
173 |
static int new_T(lua_State
*L) { |
183 |
static int gc_T(lua_State
*L) { |
184 |
if (luaL_getmetafield(L,
1, "do
not trash" ))
{ |
187 |
if (!lua_isnil(L,
-1)) return 0; |
189 |
userdataType
*ud = static_cast <userdataType*>(lua_touserdata(L,
1)); |
196 |
static int tostring_T
(lua_State *L) { |
198 |
userdataType
*ud = static_cast <userdataType*>(lua_touserdata(L,
1)); |
200 |
sprintf (buff, "%p" ,
( void *)obj); |
201 |
lua_pushfstring(L, "%s
(%s)" ,
T::className, buff); |
207 |
static void set(lua_State
*L, int table_index, const char *key)
{ |
208 |
lua_pushstring(L,
key); |
210 |
lua_settable(L,
table_index); |
214 |
static void weaktable(lua_State
*L, const char *mode)
{ |
216 |
lua_pushvalue(L,
-1); |
217 |
lua_setmetatable(L,
-2); |
218 |
lua_pushliteral(L, "__mode" ); |
219 |
lua_pushstring(L,
mode); |