该篇文章主要结合tolua.c 源码,逐行逐句分析ToLua中Wrap文件的运作原理。
适合人群:lua使用人群
阅读方式:浏览
扩展阅读:Lua C语言API
开门见山,Tolua懂的都懂,这里就不赘述了。
有关Tolua介绍和使用的文章很多,例如:点击查看
在Tolua生成的Wrap文件中,经常遇见如此写法。
// XXXWrap.cs
L.BeginStaticLibs("XXX");
L.RegFunction("Log", Log);
L.EndStaticLibs();
就很神奇的可以使用Lua来调用这些C#类的方法了。
接下来作者便深入其中,逐一分析这三句话究竟做了些什么。
首先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最后会来到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) |
栈底 |
重点来了,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);
}
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_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中函数是以闭包的形式被保存的
/* lua.h */
#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) /* 宏 压入闭包方法 n为0 */
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,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)