閱前提示
該篇文章主要結合tolua.c 源碼,逐行逐句分析ToLua中Wrap文件的運作原理。
適合人羣:lua使用人羣
閱讀方式:瀏覽
擴展閱讀:Lua C語言API
Tolua
開門見山,Tolua懂的都懂,這裏就不贅述了。
有關Tolua介紹和使用的文章很多,例如:點擊查看
Wrap
在Tolua生成的Wrap文件中,經常遇見如此寫法。
// XXXWrap.cs
L.BeginStaticLibs("XXX");
L.RegFunction("Log", Log);
L.EndStaticLibs();
就很神奇的可以使用Lua來調用這些C#類的方法了。
接下來作者便深入其中,逐一分析這三句話究竟做了些什麼。
BeginStaticLibs
首先BeginStaticLibs最終會走到這條語句上來:LuaDLL.tolua_beginstaticclass(L, name);
這語句會執行tolua dll 的C語言對應方法,tolua_beginstaticclass ,該方法 最終會在lua_State棧頂生成一個名爲name的table
/* tolua.c */
LUALIB_API void tolua_beginstaticclass(lua_State *L, const char *name)
{
lua_pushstring(L, name); /* 將name 壓入棧中,即 XXX */
lua_newtable(L);/* 創建一個table 壓入棧中*/
_addtoloaded(L);
lua_pushvalue(L, -1);
/* 這裏將棧頂表複製了一份壓入棧中 即top [XXX(table),XXX(emptytable)] bottom */
/* 以上操作相當於生成了名爲name的table :XXX = {} */
/* 以下操作抽象理解 */
/* XXX["userdata"] = &tag */
lua_pushlightuserdata(L, &tag);
lua_pushnumber(L, 1);
lua_rawset(L, -3);
/* XXX[".name"] = XXX */
lua_pushstring(L, ".name");
_pushfullname(L, -4);
lua_rawset(L, -3);
/* XXX["__index"] = static_index_event */
lua_pushstring(L, "__index");
lua_pushcfunction(L, static_index_event);
lua_rawset(L, -3);
/* XXX["__newindex"] = static_newindex_event */
lua_pushstring(L, "__newindex");
lua_pushcfunction(L, static_newindex_event);
lua_rawset(L, -3);
}
L.BeginStaticLibs("XXX");
這裏最終會在lua_State棧頂會被壓入一個名爲XXX的table
棧頂 |
---|
XXX(table) |
XXX(emptytable) |
棧底 |
EndStaticLibs
接着先把尾給收掉,EndStaticLibs最後會來到tolua.c這邊的 tolua_endstaticclass 方法,該方法最終會將棧頂元素彈出並將其設置爲 - 2位置的元表
/* tolua.c */
LUALIB_API void tolua_endstaticclass(lua_State *L)
{
lua_setmetatable(L, -2);
lua_rawset(L, -3);
}
L.EndStaticLibs();
//結束該table,彈出棧頂元素,將其設置爲XXX(emptytable)的元表
like:top(棧頂) [XXX(metatable)] bottom(棧底)
棧頂 |
---|
XXX(metatable) |
棧底 |
RegFunction
重點來了,C#方法的註冊
RegFunction先是將要註冊的方法轉換成了供平臺使用的指針,傳遞到C中生成可以供lua使用的LuaCSFunction函數。
// LuaState.cs
public void RegFunction(string name, LuaCSFunction func)
{
IntPtr fn = Marshal.GetFunctionPointerForDelegate(func);
LuaDLL.tolua_function(L, name, fn);
}
tolua.c文件中tolua_function對傳入進的函數進行了綁定。
/* tolua.c */
LUALIB_API void tolua_function(lua_State *L, const char *name, lua_CFunction fn)
{
lua_pushstring(L, name);
tolua_pushcfunction(L, fn);
lua_rawset(L, -3);
}
- lua_pushstring(L, name):壓入name => top [name,XXX(table)] bottom
- tolua_pushcfunction(L, fn) : 壓入方法fn
棧: [CClosure(f:tolua_closure,upvalue[0(false),CClosure(f:fn)]),name,XXX(table)] 下面內容會重點分析這部分究竟做了些什麼 - lua_rawset(L, -3) :賦值操作:將棧頂作爲v:CClosure,倒數第二位作爲k:name,-3位置作爲table進行賦值 ,即 XXX[name] = CClosure
C函數只要滿足lua_CFunction的樣子就可以被lua所使用。
/* tolua.c */
LUA_API int tolua_pushcfunction(lua_State *L, lua_CFunction fn)
{
lua_pushboolean(L, 0);
lua_pushcfunction(L, fn);
lua_pushcclosure(L, tolua_closure, 2);
return 0;
}
- lua_pushboolean(L, 0):壓入布爾值false top [0(false),name,XXX(table)] bottom
- lua_pushcfunction(L, fn) :壓入函數,這裏會以 CClosure結構體的形式被壓入棧
- lua_pushcclosure(L, tolua_closure, 2) : 這裏會創建一個新的CClosure結構,並將棧頂2個元素(一個布爾值與一個存放了fn的Closure)彈出並壓入CClosure結構體中,最終將新的CClosure壓入棧中
此時,lua_State棧中的表現是這樣的
棧頂 |
---|
CClosure(f:tolua_closure,upvalue[0(false),CClosure(f:fn)]) |
name:Log |
XXX(table) |
棧底 |
最終在lua_rawset(L, -3)
的作用下,變爲了這樣
棧頂 |
---|
XXX : {Log = CClosure …} |
棧底 |
到此,Tolua Wrap文件如何向Lua中註冊C#方法的過程就完畢了。實際上我們會發現,一個C#方法的指針其實被封裝了兩層:
lua_pushcfunction(L, fn)
時 將fn封裝進了CClosure中。lua_pushcclosure(L, tolua_closure, 2)
時將封裝了fn的CClosure再一次封裝進了新的CClosure中。
當我們在lua中調用註冊的方法時,實際上是在調用最外層的CClosure結構體,它其中的方法是 tolua_closure
,而我們的C#方法指針fn作爲該結構體棧中的值被存放着(upvalue)
static int tolua_closure(lua_State *L)
{
/* 獲取到我們所註冊的C#方法指針 */
lua_CFunction fn = (lua_CFunction)lua_tocfunction(L, lua_upvalueindex(2));
/* 運行 ,因爲lua跨語言的數據交互都藉助棧來完成,所以運行結果都是通過獲取棧中元素來獲得 */
int r = fn(L);
if (lua_toboolean(L, lua_upvalueindex(1)))
{
lua_pushboolean(L, 0);
lua_replace(L, lua_upvalueindex(1));
return lua_error(L);
}
return r;
}
lua_pushcfunction
實際上壓入函數的過程就是形成閉包的過程,在lua中函數是以閉包的形式被保存的
/* lua.h */
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) /* 宏 壓入閉包方法 n爲0 */
lua_pushcclosure
void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
Pushes a new C closure onto the stack.
When a C function is created, it is possible to associate some values with it, thus creating a C closure (see §3.4); these values are then accessible to the function whenever it is called. To associate values with a C function, first these values should be pushed onto the stack (when there are multiple values, the first value is pushed first). Then
lua_pushcclosure
is called to create and push the C function onto the stack, with the argumentn
telling how many values should be associated with the function.lua_pushcclosure
also pops these values from the stack.
官方地址
lua_pushcclosure:生成閉包,將函數存放在閉包結構體中,並將棧頂n個元素一同壓入閉包內的棧
/* lua.h */
LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n);
/* lapi.c */
LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
lua_lock(L);
if (n == 0) {
setfvalue(L->top, fn);
api_incr_top(L);
}
else {
CClosure *cl;
api_checknelems(L, n);
api_check(L, n <= MAXUPVAL, "upvalue index too large"); /* #define MAXUPVAL 255 */
cl = luaF_newCclosure(L, n);/* 創建了閉包結構體 */
cl->f = fn;
L->top -= n; /* 將棧頂n個元素移除並壓入閉包的棧中 upvalue */
while (n--) {
setobj2n(L, &cl->upvalue[n], L->top + n);
/* does not need barrier because closure is white */
}
setclCvalue(L, L->top, cl);
api_incr_top(L);
luaC_checkGC(L);
}
lua_unlock(L);
}
typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
TValue upvalue[1]; /* list of upvalues */
} CClosure;
.
.
.
.
.
嗨,我是作者Vin129,逐兒時之夢正在遊戲製作的技術海洋中漂泊。知道的越多,不知道的也越多。希望我的文章對你有所幫助:)