001
extern
"C"
{
002
#include
"lua.h"
003
#include
"lauxlib.h"
004
}
005
006
#define
LUNAR_DECLARE_METHOD(Class, Name) {#Name, &Class::Name}
007
008
template
<
typename
T>
class
Lunar
{
009
public
:
010
011
//C++可以向Lua注冊的函數類型
012
typedef
int
(T::*mfp)(lua_State
*L);
013
014
//向Lua中注冊的函數名字以及對應的函數地址
015
typedef
struct
{
const
char
*name;
mfp mfunc; } RegType;
016
017
//用來注冊C++定義的類供Lua使用
018
static
void
Register(lua_State
*L) {
019
//創建method
table,該table key為函數名,
020
//value為函數地址(函數可以是C++中定義類的方法,也可以在Lua中定義函數)
021
//在Lua中,以table的key為函數名,調用相應的方法。
022
lua_newtable(L);
023
int
methods
= lua_gettop(L);
024
025
//創建userdata的元表
026
luaL_newmetatable(L,
T::className);
027
int
metatable
= lua_gettop(L);
028
029
//把method
table注冊到全局表中,這樣在Lua中可以直接使用該table,
030
//這樣可以在這個table中增加Lua實現的函數
031
lua_pushvalue(L,
methods);
032
set(L,
LUA_GLOBALSINDEX, T::className);
033
034
//隱藏userdata的實質的元表,也就是說在Lua中
035
//調用getmetatable(userdata)得到的是methods
table,而不是metatable table
036
lua_pushvalue(L,
methods);
037
set(L,
metatable,
"__metatable"
);
038
039
//設置metatable
table的元方法
040
lua_pushvalue(L,
methods);
041
set(L,
metatable,
"__index"
);
042
043
lua_pushcfunction(L,
tostring_T);
044
set(L,
metatable,
"__tostring"
);
045
046
//設置__gc元方法,這樣方便在Lua回收userdata時,
047
//可以做一些其他操作,比如釋放其相應的對象
048
lua_pushcfunction(L,
gc_T);
049
set(L,
metatable,
"__gc"
);
050
051
lua_newtable(L);
//創建methods的元表mt
052
lua_pushcfunction(L,
new_T);
053
lua_pushvalue(L,
-1);
//
把new_T再次壓入棧頂
054
set(L,
methods,
"new"
);
//
把new_T函數加入到methods中,這樣腳本可通過調用T:new()來創建C++對象
055
set(L,
-3,
"__call"
);
//
mt.__call = mt,這樣腳本可以通過調用T()來創建C++對象
056
lua_setmetatable(L,
methods);
//設置methods的元表為mt
057
058
//把類T中的方法保存到method
table中,供Lua腳本使用
059
for
(RegType
*l = T::methods; l->name; l++) {
060
lua_pushstring(L,
l->name);
061
lua_pushlightuserdata(L,
(
void
*)l);
//以注冊函數在數組的位置作為cclosure的upvalue
062
lua_pushcclosure(L,
thunk, 1);
//在Lua調用的類方法,調用的都是c
closure thunk,thunk通過upvalue獲取實質調用的函數地址
063
lua_settable(L,
methods);
064
}
065
066
lua_pop(L,
2);
//彈出methods和metatable
table,保證Register調用後,棧的空間大小不變
067
}
068
069
//調用保存在method
table中的函數
070
//在調用call之前,需要向棧中壓入userdata和參數,
071
//並把最後的調用結果壓入棧中,參數method傳入要調用的函數名
072
static
int
call(lua_State
*L,
const
char
*method,
073
int
nargs=0,
int
nresults=LUA_MULTRET,
int
errfunc=0)
074
{
075
int
base
= lua_gettop(L) - nargs;
//獲取userdata在棧中的索引
076
if
(!luaL_checkudata(L,
base, T::className)) {
077
//如果用錯誤的類型調用相應的方法,則從棧中彈出userdata和參數
078
//並且壓入相應的錯誤信息
079
lua_settop(L,
base-1);
080
lua_pushfstring(L,
"not
a valid %s userdata"
,
T::className);
081
return
-1;
082
}
083
084
lua_pushstring(L,
method);
//壓入方法名,通過該名字在userdata
method table中獲取實質要調用的c closure
085
086
//獲取對應的函數地址,其流程是從userdata的元表metatable查找,
087
//而metatable.__index=methods,在methods中通過方法名,獲取相應的方法
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);
092
return
-1;
093
}
094
lua_insert(L,
base);
//
把方法移到userdata和args下面
095
096
int
status
= lua_pcall(L, 1+nargs, nresults, errfunc);
//
調用方法
097
if
(status)
{
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);
102
lua_remove(L,
base);
//
remove old message
103
return
-1;
104
}
105
return
lua_gettop(L)
- base + 1;
//
調用的方法,返回值的個數
106
}
107
108
//向棧中壓入userdata,該userdata包含一個指針,該指針指向一個類型為T的對象
109
//參數obj為指向對象的指針,參數gc默認為false,即Lua在回收userdata時,不會主動是釋放obj對應的對象,此時應用程序負責相應對象釋放
110
//若為true,則Lua在回收userdata時,會釋放相應的對象
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);
//在注冊表中獲取類名的對應的table
mt,以作為下面userdata的元表
114
if
(lua_isnil(L,
-1)) luaL_error(L,
"%s
missing metatable"
,
T::className);
115
int
mt
= lua_gettop(L);
116
117
//設置mt["userdata"]
= lookup,並向棧頂壓入lookup,lookup是一個mode為"v"的weak table,保存所有類對象對應的userdata
118
//key是對象地址,value是userdata
119
subtable(L,
mt,
"userdata"
,
"v"
);
120
userdataType
*ud =
121
static_cast
(pushuserdata(L,
obj,
sizeof
(userdataType)));
//向棧頂壓入一個userdata
122
if
(ud)
{
123
ud->pT
= obj;
//把對象的地址obj保存到userdata中
124
lua_pushvalue(L,
mt);
//壓入注冊表中類名對應的table
mt
125
lua_setmetatable(L,
-2);
//設置userdata的元表
126
if
(gc
==
false
)
{
127
//gc為false,Lua在回收userdata時,不會主動是釋放obj對應的對象,此時應用程序負責相應對象釋放
128
lua_checkstack(L,
3);
129
130
//mt["do
not trash"] = nottrash,nottrash是一個mode為"k"的weak table,保存所有不會隨userdata回收其相應對象也釋放的userdata
131
//key是userdata,value是true,向棧頂壓入nottrash
132
subtable(L,
mt,
"do
not trash"
,
"k"
);
133
lua_pushvalue(L,
-2);
//再次壓入userdata
134
lua_pushboolean(L,
1);
135
lua_settable(L,
-3);
//nottrash[userdata]
= true
136
lua_pop(L,
1);
//把nottrash從棧中彈出
137
}
138
}
139
lua_replace(L,
mt);
//把索引mt出元表值替換為userdata
140
lua_settop(L,
mt);
//設置棧的大小,即通過調用push()調用,棧頂元素為userdata,該userdata包含指向對象的指針
141
return
mt;
//返回userdata在棧中的索引
142
}
143
144
//檢測索引narg處的值是否為相應的userdata,若是則返回一個指針,該指針指向類型T的對象
145
static
T
*check(lua_State *L,
int
narg)
{
146
userdataType
*ud =
147
static_cast
(luaL_checkudata(L,
narg, T::className));
148
if
(!ud)
{
149
luaL_typerror(L,
narg, T::className);
150
return
NULL;
151
}
152
return
ud->pT;
153
}
154
155
private
:
156
157
typedef
struct
{
T *pT; } userdataType;
158
159
Lunar();
//隱藏默認的構造函數
160
161
//Lua中調用類的成員函數,都是通過調用該函數,然後使用userdataType的upvalue來調用實質的成員函數
162
static
int
thunk(lua_State
*L) {
163
//此時棧中元素是userdata和參數
164
T
*obj = check(L, 1);
//檢測是否是相應的userdata,若是,返回指向T對象的指針
165
lua_remove(L,
1);
//從棧中刪除userdata,以便成員函數的參數的索引從1開始
166
//利用upvalue獲取相應的成員函數
167
RegType
*l =
static_cast
(lua_touserdata(L,
lua_upvalueindex(1)));
168
return
(obj->*(l->mfunc))(L);
//調用實質的成員函數
169
}
170
171
//創建一個新的對象T,在腳本中調用T()或T:new(),實質調用的都是該函數
172
//調用後,棧頂元素為userdata,該userdata包含指向對象的指針
173
static
int
new_T(lua_State
*L) {
174
lua_remove(L,
1);
//
要求在腳本中使用T:new(),而不能是T.new()
175
T
*obj =
new
T(L);
//
調用類T的構造函數
176
push(L,
obj,
true
);
//
傳入true,表明Lua回收userdata時,相應的對象也會刪除
177
return
1;
178
}
179
180
//Lua在回收userdata時,相應的也會調用該函數
181
//根據userdata是否保存在nottrash(即mt["do
not trash"],mt為注冊表中T:classname對應的table)中來決定
182
//是否釋放相應的對象,若在,則不釋放相應的對象,需要應用程序自己刪除,否則刪除相應的對象
183
static
int
gc_T(lua_State
*L) {
184
if
(luaL_getmetafield(L,
1,
"do
not trash"
))
{
185
lua_pushvalue(L,
1);
//再次壓入userdata
186
lua_gettable(L,
-2);
//向棧中壓入nottrash[userdata]
187
if
(!lua_isnil(L,
-1))
return
0;
//在nottrash中,不刪除相應的對象
188
}
189
userdataType
*ud =
static_cast
(lua_touserdata(L,
1));
190
T
*obj = ud->pT;
191
if
(obj)
delete
obj;
//刪除相應的對象
192
return
0;
193
}
194
195
//在Lua中調用tostring(object)時,會調用該函數
196
static
int
tostring_T
(lua_State *L) {
197
char
buff[32];
198
userdataType
*ud =
static_cast
(lua_touserdata(L,
1));
199
T
*obj = ud->pT;
200
sprintf
(buff,
"%p"
,
(
void
*)obj);
201
lua_pushfstring(L,
"%s
(%s)"
,
T::className, buff);
202
203
return
1;
204
}
205
206
//設置t[key]=value,t是索引為table_index對應的值,value為棧頂元素
207
static
void
set(lua_State
*L,
int
table_index,
const
char
*key)
{
208
lua_pushstring(L,
key);
209
lua_insert(L,
-2);
//交換key和value
210
lua_settable(L,
table_index);
211
}
212
213
//在棧頂壓入一個模式為mode的weak
table
214
static
void
weaktable(lua_State
*L,
const
char
*mode)
{
215
lua_newtable(L);
216
lua_pushvalue(L,
-1);
217
lua_setmetatable(L,
-2);
//創建的weak
table以自身作為元表
218
lua_pushliteral(L,
"__mode"
);
219
lua_pushstring(L,
mode);
220
lua_settable(L,
-3);
//
metatable.__mode = mode
221
}
222
223
//該函數向棧中壓入值t[name],t是給定索引的tindex的值,
224
//若原來t[name]值不存在,則創建一個模式為mode的weak
table wt,並且賦值t[name] = wt
225
//最後棧頂中壓入這個weak
table
226
static
void
subtable(lua_State
*L,
int
tindex,
const
char
*name,
const
char
*mode)
{
227
lua_pushstring(L,
name);
228
lua_gettable(L,
tindex);
229
if
(lua_isnil(L,
-1)) {
230
lua_pop(L,
1);
//彈出nil
231
lua_checkstack(L,
3);
//檢測棧的空間,是否足夠
232
weaktable(L,
mode);
//棧頂壓入一個指定模式的weak
table
233
lua_pushstring(L,
name);
234
lua_pushvalue(L,
-2);
//再次壓入創建的weak
table
235
lua_settable(L,
tindex);
//t[name]
= wt
236
}
237
}
238
239
//向棧頂壓入lookup[key],lookup是一個weak
table,保存所有類對象對應的userdata
240
//key是對象地址,value是userdata,若不存在則創建一個userdata,並賦值lookup[key]
= userdata
241
//這樣使得在腳本中引用過的對象,就不會創建新的userdata了
242
static
void
*pushuserdata(lua_State
*L,
void
*key,
size_t
sz)
{
243
void
*ud
= 0;
244
lua_pushlightuserdata(L,
key);
//創建一個light
userdata
245
lua_gettable(L,
-2);
//
查找lookup[key]是否存在
246
if
(lua_isnil(L,
-1)) {
247
lua_pop(L,
1);
//彈出nil
248
lua_checkstack(L,
3);
//檢測棧的空間
249
ud
= lua_newuserdata(L, sz);
//創建一個userdata
250
lua_pushlightuserdata(L,
key);
251
lua_pushvalue(L,
-2);
//再次壓入userdata
252
lua_settable(L,
-4);
//lookup[key]
= userdata
253
}
254
return
ud;
255
}
256
};
總結
根據實際項目需要,可能還需要其他功能,比如在腳本中獲取和設置對象的成員變量、處理類的繼承等需求。只要理解C++對象綁定到Lua方法,就可以方便按需要擴展,或者快速修改第三方庫以滿足需求。
參考資料