ToLua:逐行分析源码,搞清楚Wrap文件原理

阅前提示

该篇文章主要结合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) 时将封装了fnCClosure再一次封装进了新的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 argument n 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,逐儿时之梦正在游戏制作的技术海洋中漂泊。知道的越多,不知道的也越多。希望我的文章对你有所帮助:)


猜你喜欢

转载自blog.csdn.net/qq_28820675/article/details/106864636
今日推荐