
正文
浅析C++绑定到Lua的方法
提示:扫一扫查出行【扫一扫了解最新限行尾号】
复制提示
注:原文也在公司内部论坛上发了
概述
尽管将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的測试代码。例如以下:
001
|
extern
"C"
{
|
002
|
#include
|
003
|
#include
|
004
|
}
|
005
|
006
|
#define
|
007
|
008
|
template
<
typename
T>
class
Lunar
|
009
|
public
:
|
010
|
011
|
//C++能够向Lua注冊的函数类型
|
012
|
typedef
int
(T::*mfp)(lua_State
|
013
|
014
|
//向Lua中注冊的函数名字以及相应的函数地址
|
015
|
typedef
struct
{
const
char
*name;
|
016
|
017
|
//用来注冊C++定义的类供Lua使用
|
018
|
static
void
Register(lua_State
|
019
|
//创建method
|
020
|
//value为函数地址(函数能够是C++中定义类的方法,也能够在Lua中定义函数)
|
021
|
//在Lua中,以table的key为函数名。调用对应的方法。
|
022
|
lua_newtable(L);
|
023
|
int
methods
|
024
|
025
|
//创建userdata的元表
|
026
|
luaL_newmetatable(L,
|
027
|
int
metatable
|
028
|
029
|
//把method
|
030
|
//这样能够在这个table中添加Lua实现的函数
|
031
|
lua_pushvalue(L,
|
032
|
set(L,
|
033
|
034
|
//隐藏userdata的实质的元表,也就是说在Lua中
|
035
|
//调用getmetatable(userdata)得到的是methods
|
036
|
lua_pushvalue(L,
|
037
|
set(L,
"__metatable"
);
|
038
|
039
|
//设置metatable
|
040
|
lua_pushvalue(L,
|
041
|
set(L,
"__index"
);
|
042
|
043
|
lua_pushcfunction(L,
|
044
|
set(L,
"__tostring"
);
|
045
|
|
046
|
//设置__gc元方法。这样方便在Lua回收userdata时,
|
047
|
//能够做一些其它操作。比方释放其对应的对象
|
048
|
lua_pushcfunction(L,
|
049
|
set(L,
"__gc"
);
|
050
|
051
|
lua_newtable(L);
//创建methods的元表mt
|
052
|
lua_pushcfunction(L,
|
053
|
lua_pushvalue(L,
//
|
054
|
set(L,
"new"
);
//
|
055
|
set(L,
"__call"
);
//
|
056
|
lua_setmetatable(L,
//设置methods的元表为mt
|
057
|
058
|
//把类T中的方法保存到method
|
059
|
for
(RegType
|
060
|
lua_pushstring(L,
|
061
|
lua_pushlightuserdata(L,
void
*)l);
//以注冊函数在数组的位置作为cclosure的upvalue
|
062
|
lua_pushcclosure(L,
//在Lua调用的类方法,调用的都是c
|
063
|
lua_settable(L,
|
064
|
}
|
065
|
066
|
lua_pop(L,
//弹出methods和metatable
|
067
|
}
|
068
|
069
|
//调用保存在method
|
070
|
//在调用call之前。须要向栈中压入userdata和參数。
|
071
|
//并把最后的调用结果压入栈中,參数method传入要调用的函数名
|
072
|
static
int
call(lua_State
const
char
*method,
|
073
|
int
nargs=0,
int
nresults=LUA_MULTRET,
int
errfunc=0)
|
074
|
{
|
075
|
int
base
//获取userdata在栈中的索引
|
076
|
if
(!luaL_checkudata(L,
|
077
|
//假设用错误的类型调用对应的方法。则从栈中弹出userdata和參数
|
078
|
//而且压入对应的错误信息
|
079
|
lua_settop(L,
|
080
|
lua_pushfstring(L,
"not
,
|
081
|
return
-1;
|
082
|
}
|
083
|
084
|
lua_pushstring(L,
//压入方法名,通过该名字在userdata
|
085
|
086
|
//获取相应的函数地址。其流程是从userdata的元表metatable查找,
|
087
|
//而metatable.__index=methods,在methods中通过方法名,获取对应的方法
|
088
|
lua_gettable(L,
|
089
|
if
(lua_isnil(L,
//若不存在对应的方法
|
090
|
lua_settop(L,
|
091
|
lua_pushfstring(L,
"%s
,
|
092
|
return
-1;
|
093
|
}
|
094
|
lua_insert(L,
//
|
095
|
096
|
int
status
//
|
097
|
if
(status)
|
098
|
const
char
*msg
|
099
|
if
(msg
"(error
;
|
100
|
lua_pushfstring(L,
"%s:%s
,
|
101
|
T::className,
|
102
|
lua_remove(L,
//
|
103
|
return
-1;
|
104
|
}
|
105
|
return
lua_gettop(L)
//
|
106
|
}
|
107
|
108
|
//向栈中压入userdata,该userdata包括一个指针,该指针指向一个类型为T的对象
|
109
|
//參数obj为指向对象的指针,參数gc默觉得false,即Lua在回收userdata时,不会主动是释放obj相应的对象,此时应用程序负责相应对象释放
|
110
|
//若为true,则Lua在回收userdata时。会释放对应的对象
|
111
|
static
int
push(lua_State
bool
gc=
false
)
|
112
|
if
(!obj)
return
0;
|
113
|
luaL_getmetatable(L,
//在注冊表中获取类名的相应的table
|
114
|
if
(lua_isnil(L,
"%s
,
|
115
|
int
mt
|
116
|
117
|
//设置mt["userdata"]
|
118
|
//key是对象地址,value是userdata
|
119
|
subtable(L,
"userdata"
,
"v"
);
|
120
|
userdataType
|
121
|
static_cast
<userdataType*>(pushuserdata(L,
sizeof
(userdataType)));
//向栈顶压入一个userdata
|
122
|
if
(ud)
|
123
|
ud->pT
//把对象的地址obj保存到userdata中
|
124
|
lua_pushvalue(L,
//压入注冊表中类名相应的table
|
125
|
lua_setmetatable(L,
//设置userdata的元表
|
126
|
if
(gc
false
)
|
127
|
//gc为false。Lua在回收userdata时,不会主动是释放obj相应的对象,此时应用程序负责相应对象释放
|
128
|
lua_checkstack(L,
|
129
|
130
|
//mt["do
|
131
|
//key是userdata,value是true。向栈顶压入nottrash
|
132
|
subtable(L,
"do
,
"k"
);
|
133
|
lua_pushvalue(L,
//再次压入userdata
|
134
|
lua_pushboolean(L,
|
135
|
lua_settable(L,
//nottrash[userdata]
|
136
|
lua_pop(L,
//把nottrash从栈中弹出
|
137
|
}
|
138
|
}
|
139
|
lua_replace(L,
//把索引mt出元表值替换为userdata
|
140
|
lua_settop(L,
//设置栈的大小。即通过调用push()调用,栈顶元素为userdata,该userdata包括指向对象的指针
|
141
|
return
mt;
//返回userdata在栈中的索引
|
142
|
}
|
143
|
144
|
//检測索引narg处的值是否为对应的userdata,若是则返回一个指针。该指针指向类型T的对象
|
145
|
static
T
int
narg)
|
146
|
userdataType
|
147
|
static_cast
<userdataType*>(luaL_checkudata(L,
|
148
|
if
(!ud)
|
149
|
luaL_typerror(L,
|
150
|
return
NULL;
|
151
|
}
|
152
|
return
ud->pT;
|
153
|
}
|
154
|
155
|
private
:
|
156
|
157
|
typedef
struct
{
|
158
|
159
|
Lunar();
//隐藏默认的构造函数
|
160
|
161
|
//Lua中调用类的成员函数,都是通过调用该函数,然后使用userdataType的upvalue来调用实质的成员函数
|
162
|
static
int
thunk(lua_State
|
163
|
//此时栈中元素是userdata和參数
|
164
|
T
//检測是否是对应的userdata,若是,返回指向T对象的指针
|
165
|
lua_remove(L,
//从栈中删除userdata。以便成员函数的參数的索引从1開始
|
166
|
//利用upvalue获取对应的成员函数
|
167
|
RegType
static_cast
<RegType*>(lua_touserdata(L,
|
168
|
return
(obj->*(l->mfunc))(L);
//调用实质的成员函数
|
169
|
}
|
170
|
171
|
//创建一个新的对象T。在脚本中调用T()或T:new(),实质调用的都是该函数
|
172
|
//调用后,栈顶元素为userdata,该userdata包括指向对象的指针
|
173
|
static
int
new_T(lua_State
|
174
|
lua_remove(L,
//
|
175
|
T
new
T(L);
//
|
176
|
push(L,
true
);
//
|
177
|
return
1;
|
178
|
}
|
179
|
180
|
//Lua在回收userdata时,对应的也会调用该函数
|
181
|
//依据userdata是否保存在nottrash(即mt["do
|
182
|
//是否释放对应的对象,若在,则不释放对应的对象,须要应用程序自己删除,否则删除对应的对象
|
183
|
static
int
gc_T(lua_State
|
184
|
if
(luaL_getmetafield(L,
"do
))
|
185
|
lua_pushvalue(L,
//再次压入userdata
|
186
|
lua_gettable(L,
//向栈中压入nottrash[userdata]
|
187
|
if
(!lua_isnil(L,
return
0;
//在nottrash中,不删除对应的对象
|
188
|
}
|
189
|
userdataType
static_cast
<userdataType*>(lua_touserdata(L,
|
190
|
T
|
191
|
if
(obj)
delete
obj;
//删除对应的对象
|
192
|
return
0;
|
193
|
}
|
194
|
195
|
//在Lua中调用tostring(object)时,会调用该函数
|
196
|
static
int
tostring_T
|
197
|
char
buff[32];
|
198
|
userdataType
static_cast
<userdataType*>(lua_touserdata(L,
|
199
|
T
|
200
|
sprintf
(buff,
"%p"
,
void
*)obj);
|
201
|
lua_pushfstring(L,
"%s
,
|
202
|
203
|
return
1;
|
204
|
}
|
205
|
206
|
//设置t[key]=value,t是索引为table_index相应的值,value为栈顶元素
|
207
|
static
void
set(lua_State
int
table_index,
const
char
*key)
|
208
|
lua_pushstring(L,
|
209
|
lua_insert(L,
//交换key和value
|
210
|
lua_settable(L,
|
211
|
}
|
212
|
213
|
//在栈顶压入一个模式为mode的weak
|
214
|
static
void
weaktable(lua_State
const
char
*mode)
|
215
|
lua_newtable(L);
|
216
|
lua_pushvalue(L,
|
217
|
lua_setmetatable(L,
//创建的weak
|
218
|
lua_pushliteral(L,
"__mode"
);
|
219
|
lua_pushstring(L,
|
220
|
lua_settable(L,
//
|
221
|
}
|
222
|
223
|
//该函数向栈中压入值t[name]。t是给定索引的tindex的值,
|
224
|
//若原来t[name]值不存在,则创建一个模式为mode的weak
|
225
|
//最后栈顶中压入这个weak
|
226
|
|







