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 <userdataType*>(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 <userdataType*>(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 <RegType*>(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 <userdataType*>(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 <userdataType*>(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方法,就可以方便按需要扩展,或者快速修改第三方库以满足需求。
参考资料
原文地址:http://blog.csdn.net/maximuszhou/article/details/40866507