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方法,就可以方便按需要擴展,或者快速修改第三方庫以滿足需求。
參考資料