浅析C++绑定到Lua的方法
本文通过分析第三方库Lunar(我们游戏服务端用的是Luna,Lunar是Luna添加版。但仍然足够简洁)的实现,来理解C++对象绑定到Lua的通常方法。Lunar的測试代码放在我的github上。
在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++自己释放所创建的对象。
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 |
|