起因
在 log 里面看到一条报错,大概情况如下,在 socket 回调里面报了一条 attempt to call a nil value(大意)。然后类结构大概是这样的:
local MessageLayer = class("MessageLayer", CC.Layer)
function MessageLayer:showDisappearEffect()
-- do something
end
local a = class("layer", MessageLayer)
local scene = cc.Director:getInstance():getRunningScene()
local instance = a:create()
instance:addTo(scene)
然后这个 instance 被移除的情况下,它上面的 luafunction 调不到了。但是这个 instance 本身还是存在的。所以我们关注的问题是:在 lua 里,对于一个 userData 的实例,当 C++ 对象析构了之后,为什么它上面的 lua 方法会失效
尝试解决
打印了一下 userdata 的原表,大概这样写:
function ThemeScene:onEnter( ) for k,v in pairs(getmetatable(self)) do print(k,v) end end
打印出的结果和我想的不太一样。
[LUA-print] getDefaultCamera function: 0x133752a0 [LUA-print] __index function: 0x13371448 [LUA-print] __newindex function: 0x13371480 [LUA-print] __gc function: 0x1336a220 [LUA-print] __eq function: 0x1336bad8 [LUA-print] __lt function: 0x1336bbb0 [LUA-print] __le function: 0x1336bbe8 [LUA-print] getNavMesh function: 0x13498df0 [LUA-print] __call function: 0x13374888 [LUA-print] __add function: 0x1336baa0 [LUA-print] __sub function: 0x13371410 [LUA-print] __mul function: 0x1336bb40 [LUA-print] __div function: 0x1336bb78 [LUA-print] setNavMesh function: 0x13498d88 [LUA-print] tolua_ubox table: 0x1336c028 [LUA-print] setNavMeshDebugCamera function: 0x13498d20 [LUA-print] setPhysics3DDebugCamera function: 0x13498cb0 [LUA-print] getPhysics3DWorld function: 0x13498c40 [LUA-print] createWithPhysics function: 0x133753b0 [LUA-print] create function: 0x13375340 [LUA-print] .classname cc.Scene [LUA-print] createWithSize function: 0x13375308 [LUA-print] onProjectionChanged function: 0x13374a00 [LUA-print] initWithPhysics function: 0x1336e0c0 [LUA-print] setCameraOrderDirty function: 0x13374e90 [LUA-print] render function: 0x13374ef0 [LUA-print] stepPhysicsAndNavigation function: 0x13374990 [LUA-print] new function: 0x1336df70 [LUA-print] getPhysicsWorld function: 0x13374a68 [LUA-print] initWithSize function: 0x13375230
明显可以看出来,基本上所有的东西都是 c++ 里的方法,也就是 userdata 里的方法,那么我们自己定义的一些 lua 方法去哪里了呢?
前置知识
其实方向一开始就错了。本身 userdata 上面其实不应该直接绑定任何 lua 的东西的,正确的做法应该是套一层,然后在套娃的那个 luatable 里来做事,这个套娃一般称之为 peer
c++_obj 的 metatable 的__index 指向一个 c 函数,当访问 c++_obj 中的一个域的时候,会调用这个 c 函数,这个 c 函数会去查找各个关联表,来取得我们要访问的域,这其中就包括对 peer 表的查询。
具体解决
另外,在 functions.lua 里其实也可以找到证据
先看下 class 方法的完整代码
function class(classname, ...) local cls = {__cname = classname} local supers = {...} for _, super in ipairs(supers) do local superType = type(super) assert(superType == "nil" or superType == "table" or superType == "function", string.format("class() - create class \"%s\" with invalid super class type \"%s\"", classname, superType)) if superType == "function" then assert(cls.__create == nil, string.format("class() - create class \"%s\" with more than one creating function", classname)); -- if super is function, set it to __create cls.__create = super elseif superType == "table" then if super[".isclass"] then -- super is native class assert(cls.__create == nil, string.format("class() - create class \"%s\" with more than one creating function or native class", classname)); cls.__create = function() return super:create() end else -- super is pure lua class cls.__supers = cls.__supers or {} cls.__supers[#cls.__supers + 1] = super if not cls.super then -- set first super pure lua class as class.super cls.super = super end end else error(string.format("class() - create class \"%s\" with invalid super type", classname), 0) end end cls.__index = cls if not cls.__supers or #cls.__supers == 1 then setmetatable(cls, {__index = cls.super}) else setmetatable(cls, {__index = function(_, key) local supers = cls.__supers for i = 1, #supers do local super = supers[i] if super[key] then return super[key] end end end}) end if not cls.ctor then -- add default constructor cls.ctor = function() end end cls.new = function(...) local instance if cls.__create then instance = cls.__create(...) else instance = {} end setmetatableindex(instance, cls) instance.class = cls instance:ctor(...) return instance end cls.create = function(_, ...) return cls.new(...) end return cls end
关键关注 setmetatableindex
这里。
他的定义是这样的:
local setmetatableindex_ setmetatableindex_ = function(t, index) if type(t) == "userdata" then local peer = tolua.getpeer(t) if not peer then peer = {} tolua.setpeer(t, peer) end setmetatableindex_(peer, index) else local mt = getmetatable(t) if not mt then mt = {} end if not mt.__index then mt.__index = index setmetatable(t, mt) elseif mt.__index ~= index then setmetatableindex_(mt, index) end end end setmetatableindex = setmetatableindex_
其实看到这里就很明显了,所有对 self 所做的操作其实都是放在这个 peer 表中的。
首先简单些一个 lua 类,继承自 cc.Layer,并且自己在 lua 里面定义一些方法
local testLayer = class("layerClass",cc.Layer) function testLayer:testFunc() print("testFunc") end
然后我们将他实例化:
... local layer = testLayer:create()
这个过程发生的事情,可以对照 class 方法里的东西
if not cls.ctor then -- add default constructor cls.ctor = function() end end cls.new = function(...) local instance if cls.__create then instance = cls.__create(...) else instance = {} end setmetatableindex(instance, cls) instance.class = cls instance:ctor(...) return instance end cls.create = function(_, ...) return cls.new(...) end
重点在于这个 new 方法里,如果他有__create 方法,那么就用__create 来创建实例。在此例中,便是 cc.Layer 的 create 方法。它返回了一个 layer 的 userData 实例。
接下来的关键,是这个 setmetatableindex
,这是一个针对 userdata 重新定义的一个方法。看下它的实现
setmetatableindex_ = function(t, index) if type(t) == "userdata" then local peer = tolua.getpeer(t) if not peer then peer = {} tolua.setpeer(t, peer) end setmetatableindex_(peer, index) else local mt = getmetatable(t) if not mt then mt = {} end if not mt.__index then mt.__index = index setmetatable(t, mt) elseif mt.__index ~= index then setmetatableindex_(mt, index) end end end setmetatableindex = setmetatableindex_
也很好懂,首先如果是要对一个 userdata 设置 metatable,不可以直接设置,而是去创建一个这个 userdata 的 peer 表。这个 peer 表可以看作是一个纯粹的 luatable,之后所有的操作都是在这个 peer 表上进行的。对 userdata 做索引,不但可以索引到它自己导出的 C++ 方法,也可以索引到这个 peer 表。那么,对于我们上面例子所定义的 testLayer:testFunc
它其实就是关联到了 peer 表上。
那么可以有一个猜想,当 C++ 对象析构的时候,是不是这个 peer 表被清掉了导致找不到 lua 方法呢?
简单写个例子佐证一下。
local node = cc.Node:create() node:addTo(self) node._val = 1000 print("<<<<<<<<<<<<<<") print("node._peer",tolua.getpeer(node)) print("node._val:",node._val) print("<<<<<<<<<<<<<<") self:delayCall(0.01,function() node:removeFromParent() print("===========") print("node._peer",tolua.getpeer(node)) print("node._val:",node._val) print("===========") end)
之后的输出验证了这点:
[LUA-print] <<<<<<<<<<<<<< [LUA-print] node._peer table: 0x1fcb8e50 [LUA-print] node._val: 1000 [LUA-print] <<<<<<<<<<<<<<
[LUA-print] =========== [LUA-print] node._peer nil [LUA-print] node._val: nil [LUA-print] ===========
C++ 内部实现
清理的过程
那么当一个 CCRef removeFromParent 之后,为什么 peer 表会被清除呢。
Ref::~Ref() { ScriptEngineProtocol* pEngine = ScriptEngineManager::getInstance()->getScriptEngine(); if (pEngine != nullptr && _luaID) { // if the object is referenced by Lua engine, remove it pEngine->removeScriptObjectByObject(this); } ...
luaEngine 里的代码
void LuaEngine::removeScriptObjectByObject(Ref* pObj) { _stack->removeScriptObjectByObject(pObj); ScriptHandlerMgr::getInstance()->removeObjectAllHandlers(pObj); }
关于 scripthandler 的东西我们不关心,我们关心的其实是这一句
_stack->removeScriptObjectByObject(pObj);
顺着继续看下去
void LuaStack::removeScriptObjectByObject(Ref* pObj) { toluafix_remove_ccobject_by_refid(_state, pObj->_luaID); }
再继续
TOLUA_API int toluafix_remove_ccobject_by_refid(lua_State* L, int refid) { void* ptr = NULL; const char* type = NULL; void** ud = NULL; if (refid == 0) return -1; // get ptr from tolua_refid_ptr_mapping lua_pushstring(L, TOLUA_REFID_PTR_MAPPING); lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_ptr */ lua_pushinteger(L, refid); /* stack: refid_ptr refid */ lua_rawget(L, -2); /* stack: refid_ptr ptr */ ptr = lua_touserdata(L, -1); lua_pop(L, 1); /* stack: refid_ptr */ if (ptr == NULL) { lua_pop(L, 1); // Lua stack has closed, C++ object not in Lua. // printf("[LUA ERROR] remove CCObject with NULL ptr, refid: %d\n", refid); return -2; } // remove ptr from tolua_refid_ptr_mapping lua_pushinteger(L, refid); /* stack: refid_ptr refid */ lua_pushnil(L); /* stack: refid_ptr refid nil */ lua_rawset(L, -3); /* delete refid_ptr[refid], stack: refid_ptr */ lua_pop(L, 1); /* stack: - */ // get type from tolua_refid_type_mapping lua_pushstring(L, TOLUA_REFID_TYPE_MAPPING); lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_type */ lua_pushinteger(L, refid); /* stack: refid_type refid */ lua_rawget(L, -2); /* stack: refid_type type */ if (lua_isnil(L, -1)) { lua_pop(L, 2); printf("[LUA ERROR] remove CCObject with NULL type, refid: %d, ptr: %p\n", refid, ptr); return -1; } type = lua_tostring(L, -1); lua_pop(L, 1); /* stack: refid_type */ // remove type from tolua_refid_type_mapping lua_pushinteger(L, refid); /* stack: refid_type refid */ lua_pushnil(L); /* stack: refid_type refid nil */ lua_rawset(L, -3); /* delete refid_type[refid], stack: refid_type */ lua_pop(L, 1); /* stack: - */ // get ubox luaL_getmetatable(L, type); /* stack: mt */ lua_pushstring(L, "tolua_ubox"); /* stack: mt key */ lua_rawget(L, -2); /* stack: mt ubox */ if (lua_isnil(L, -1)) { // use global ubox lua_pop(L, 1); /* stack: mt */ lua_pushstring(L, "tolua_ubox"); /* stack: mt key */ lua_rawget(L, LUA_REGISTRYINDEX); /* stack: mt ubox */ }; // cleanup root tolua_remove_value_from_root(L, ptr); lua_pushlightuserdata(L, ptr); /* stack: mt ubox ptr */ lua_rawget(L,-2); /* stack: mt ubox ud */ if (lua_isnil(L, -1)) { // Lua object has released (GC), C++ object not in ubox. //printf("[LUA ERROR] remove CCObject with NULL ubox, refid: %d, ptr: %x, type: %s\n", refid, (int)ptr, type); lua_pop(L, 3); return -3; } // cleanup peertable lua_pushvalue(L, LUA_REGISTRYINDEX); lua_setfenv(L, -2); ud = (void**)lua_touserdata(L, -1); lua_pop(L, 1); /* stack: mt ubox */ if (ud == NULL) { printf("[LUA ERROR] remove CCObject with NULL userdata, refid: %d, ptr: %p, type: %s\n", refid, ptr, type); lua_pop(L, 2); return -1; } // clean userdata *ud = NULL; lua_pushlightuserdata(L, ptr); /* stack: mt ubox ptr */ lua_pushnil(L); /* stack: mt ubox ptr nil */ lua_rawset(L, -3); /* ubox[ptr] = nil, stack: mt ubox */ lua_pop(L, 2); //printf("[LUA] remove CCObject, refid: %d, ptr: %x, type: %s\n", refid, (int)ptr, type); return 0; }
这里清除了好几个东西,我们一个个来看。对 lua 栈操作不熟悉的可以参考这个参考文档
如果需要看 5.1 的参考,参考这个文档
首先明确的是,这个 refid 是 CCRef 里的 luaid。
先看下第一部分
// get ptr from tolua_refid_ptr_mapping lua_pushstring(L, TOLUA_REFID_PTR_MAPPING); lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_ptr */ lua_pushinteger(L, refid); /* stack: refid_ptr refid */ lua_rawget(L, -2); /* stack: refid_ptr ptr */ ptr = lua_touserdata(L, -1); lua_pop(L, 1); /* stack: refid_ptr */ if (ptr == NULL) { lua_pop(L, 1); // Lua stack has closed, C++ object not in Lua. // printf("[LUA ERROR] remove CCObject with NULL ptr, refid: %d\n", refid); return -2; }
tolua 在 lua5.1 的 binding 里面有一个重要的概念就是这个 LUA_REGISTRYINDEX
为 index 的全局注册表。我们姑且把这个表称之为为 Reg,暂时把他理解为一个 luatable。
通过阅读可以知道,他其实是拿出 Reg[TOLUA_REFID_PTR_MAPPING
][refid
]存到 ptr 这个变量里。
接下来,它清除了 Reg[TOLUA_REFID_PTR_MAPPING
][refid
],也就是相当于调用了 Reg[TOLUA_REFID_PTR_MAPPING
][refid
] = nil
// remove ptr from tolua_refid_ptr_mapping lua_pushinteger(L, refid); /* stack: refid_ptr refid */ lua_pushnil(L); /* stack: refid_ptr refid nil */ lua_rawset(L, -3); /* delete refid_ptr[refid], stack: refid_ptr */ lua_pop(L, 1); /* stack: - */
接下来的代码同理,只是换了一个表清.这次取得是 Reg[``TOLUA_REFID_TYPE_MAPPING ][
refid`],里面存着一个 string.
// get type from tolua_refid_type_mapping lua_pushstring(L, TOLUA_REFID_TYPE_MAPPING); lua_rawget(L, LUA_REGISTRYINDEX); /* stack: refid_type */ lua_pushinteger(L, refid); /* stack: refid_type refid */ lua_rawget(L, -2); /* stack: refid_type type */ if (lua_isnil(L, -1)) { lua_pop(L, 2); printf("[LUA ERROR] remove CCObject with NULL type, refid: %d, ptr: %p\n", refid, ptr); return -1; } type = lua_tostring(L, -1); lua_pop(L, 1); /* stack: refid_type */ // remove type from tolua_refid_type_mapping lua_pushinteger(L, refid); /* stack: refid_type refid */ lua_pushnil(L); /* stack: refid_type refid nil */ lua_rawset(L, -3); /* delete refid_type[refid], stack: refid_type */ lua_pop(L, 1); /* stack: - */
接下来是拿出 ubox 表。
这个 ubox 表有两种存的方法,有可能存在 Reg[type]["tolua_ubox"]里,也有可能直接存在 Reg[“tolua_ubox”]。如果前者不存在,那么就拿后者。
// get ubox luaL_getmetatable(L, type); /* stack: mt */ lua_pushstring(L, "tolua_ubox"); /* stack: mt key */ lua_rawget(L, -2); /* stack: mt ubox */ if (lua_isnil(L, -1)) { // use global ubox lua_pop(L, 1); /* stack: mt */ lua_pushstring(L, "tolua_ubox"); /* stack: mt key */ lua_rawget(L, LUA_REGISTRYINDEX); /* stack: mt ubox */ };
之后,拿着这个 ubox 表,清除 tolua_value_from_root 这个表,也就是 tolua_remove_value_from_root
方法里写的。具体如下
tolua_remove_value_from_root(L, ptr);
具体定义
其实只干了一件事,那就是执行了 Reg[TOLUA_VALUE_ROOT
][prt]=nil,也就是把 value_root 表里的 ptr 对应的项置空了。
TOLUA_API void tolua_remove_value_from_root (lua_State* L, void* ptr) { lua_pushstring(L, TOLUA_VALUE_ROOT); lua_rawget(L, LUA_REGISTRYINDEX); /* stack: root */ lua_pushlightuserdata(L, ptr); /* stack: root ptr */ lua_pushnil(L); /* stack: root ptr nil */ lua_rawset(L, -3); /* root[ptr] = nil, stack: root */ lua_pop(L, 1); }
接下来,就要在 ubox 表里取出来我们 ptr 所对应的值
lua_pushlightuserdata(L, ptr); /* stack: mt ubox ptr */ lua_rawget(L,-2); /* stack: mt ubox ubox[ptr] */ if (lua_isnil(L, -1)) { // Lua object has released (GC), C++ object not in ubox. //printf("[LUA ERROR] remove CCObject with NULL ubox, refid: %d, ptr: %x, type: %s\n", refid, (int)ptr, type); lua_pop(L, 3); return -3; }
接下来就是我们的重点,peer 表。此时我们的栈顶是 reg ubox ubox[ptr]
这里先把 lua 的全局注册表 push 进站,然后把他作为 ubox[ptr]的新的 lua_env
// cleanup peertable
lua_pushvalue(L, LUA_REGISTRYINDEX);
lua_setfenv(L, -2);
然后在注册表的 env 之下,把顶层指向 userdata 的指针,也就是 ubox[ptr]取出来,把它所指向的 userdata 置空。
ud = (void**)lua_touserdata(L, -1); lua_pop(L, 1); /* stack: mt ubox */ if (ud == NULL) { printf("[LUA ERROR] remove CCObject with NULL userdata, refid: %d, ptr: %p, type: %s\n", refid, ptr, type); lua_pop(L, 2); return -1; } // clean userdata *ud = NULL;
最后,再把 ubox 里以 ptr 为 key 的值置空。ubox[ptr]=nil
lua_pushlightuserdata(L, ptr); /* stack: mt ubox ptr */
lua_pushnil(L); /* stack: mt ubox ptr nil */
lua_rawset(L, -3); /* ubox[ptr] = nil, stack: mt ubox */
至此,整个清理过程完成。
创建绑定的过程
经过上面代码的阅读我们明白了 lua 部分是如何清理的,对需要清理哪些表有了一个大概的了解。但是我们还需要知道一件事情:那就是我们的 userdata 是怎么调到 peer 表的。
那么相应的,我们需要看一下在绑定过程中,userdata 到底需要绑定哪些东西。
从开始注册绑定开始
其实观察下 lua 的注册过程,会发现这个东西:
TOLUA_API void tolua_usertype (lua_State* L, const char* type)
{
char ctype[128] = "const ";
strncat(ctype,type,120);
/* create both metatables */
if (tolua_newmetatable(L,ctype) && tolua_newmetatable(L,type))
mapsuper(L,type,ctype); /* 'type' is also a 'const type' */
}
然后重点是 tolua_newmetatable
static int tolua_newmetatable (lua_State* L, const char* name)
{
int r = luaL_newmetatable(L,name);
#ifdef LUA_VERSION_NUM /* only lua 5.1 */
if (r) {
lua_pushvalue(L, -1);
lua_pushstring(L, name);
lua_settable(L, LUA_REGISTRYINDEX); /* reg[mt] = type_name */
};
#endif
if (r)
tolua_classevents(L); /* set meta events */
// metatable[".classname"] = name
lua_pushliteral(L, ".classname"); // stack: metatable ".classname"
lua_pushstring(L, name); // stack: metatable ".classname" name
lua_rawset(L, -3); // stack: metatable
lua_pop(L,1);
return r;
}
其中的 tolua_classevents
TOLUA_API void tolua_classevents (lua_State* L)
{
lua_pushstring(L,"__index");
lua_pushcfunction(L,class_index_event);
lua_rawset(L,-3);
lua_pushstring(L,"__newindex");
lua_pushcfunction(L,class_newindex_event);
lua_rawset(L,-3);
lua_pushstring(L,"__add");
lua_pushcfunction(L,class_add_event);
lua_rawset(L,-3);
lua_pushstring(L,"__sub");
lua_pushcfunction(L,class_sub_event);
lua_rawset(L,-3);
lua_pushstring(L,"__mul");
lua_pushcfunction(L,class_mul_event);
lua_rawset(L,-3);
lua_pushstring(L,"__div");
lua_pushcfunction(L,class_div_event);
lua_rawset(L,-3);
lua_pushstring(L,"__lt");
lua_pushcfunction(L,class_lt_event);
lua_rawset(L,-3);
lua_pushstring(L,"__le");
lua_pushcfunction(L,class_le_event);
lua_rawset(L,-3);
lua_pushstring(L,"__eq");
lua_pushcfunction(L,class_eq_event);
lua_rawset(L,-3);
lua_pushstring(L,"__call");
lua_pushcfunction(L,class_call_event);
lua_rawset(L,-3);
lua_pushstring(L,"__gc");
lua_pushstring(L, "tolua_gc_event");
lua_rawget(L, LUA_REGISTRYINDEX);
/*lua_pushcfunction(L,class_gc_event);*/
lua_rawset(L,-3);
}
是不是这里的内容就很熟悉了,正如我们正常操作原表来做继承的过程。只不过这里很多东西都会存到注册表里,比纯粹 lua 里面的继承要复杂很多。
那当然我们要看下熟悉的__index 方法注册的静态方法。
static int class_index_event (lua_State* L) { int t = lua_type(L,1); if (t == LUA_TUSERDATA) { /* Access alternative table */ #ifdef LUA_VERSION_NUM /* new macro on version 5.1 */ lua_getfenv(L,1); if (!lua_rawequal(L, -1, TOLUA_NOPEER)) { lua_pushvalue(L, 2); /* key */ lua_gettable(L, -2); /* on lua 5.1, we trade the "tolua_peers" lookup for a gettable call */ if (!lua_isnil(L, -1)) return 1; }; #else lua_pushstring(L,"tolua_peers"); lua_rawget(L,LUA_REGISTRYINDEX); /* stack: obj key ubox */ lua_pushvalue(L,1); lua_rawget(L,-2); /* stack: obj key ubox ubox[u] */ if (lua_istable(L,-1)) { lua_pushvalue(L,2); /* key */ lua_rawget(L,-2); /* stack: obj key ubox ubox[u] value */ if (!lua_isnil(L,-1)) return 1; } #endif lua_settop(L,2); /* stack: obj key */ /* Try metatables */ lua_pushvalue(L,1); /* stack: obj key obj */ while (lua_getmetatable(L,-1)) { /* stack: obj key obj mt */ lua_remove(L,-2); /* stack: obj key mt */ if (lua_isnumber(L,2)) /* check if key is a numeric value */ { /* try operator[] */ lua_pushstring(L,".geti"); lua_rawget(L,-2); /* stack: obj key mt func */ if (lua_isfunction(L,-1)) { lua_pushvalue(L,1); lua_pushvalue(L,2); lua_call(L,2,1); return 1; } } else { lua_pushvalue(L,2); /* stack: obj key mt key */ lua_rawget(L,-2); /* stack: obj key mt value */ if (!lua_isnil(L,-1)) return 1; else lua_pop(L,1); /* try C/C++ variable */ lua_pushstring(L,".get"); lua_rawget(L,-2); /* stack: obj key mt tget */ if (lua_istable(L,-1)) { lua_pushvalue(L,2); lua_rawget(L,-2); /* stack: obj key mt value */ if (lua_iscfunction(L,-1)) { lua_pushvalue(L,1); lua_pushvalue(L,2); lua_call(L,2,1); return 1; } else if (lua_istable(L,-1)) { /* deal with array: create table to be returned and cache it in ubox */ void* u = *((void**)lua_touserdata(L,1)); lua_newtable(L); /* stack: obj key mt value table */ lua_pushstring(L,".self"); lua_pushlightuserdata(L,u); lua_rawset(L,-3); /* store usertype in ".self" */ lua_insert(L,-2); /* stack: obj key mt table value */ lua_setmetatable(L,-2); /* set stored value as metatable */ lua_pushvalue(L,-1); /* stack: obj key met table table */ lua_pushvalue(L,2); /* stack: obj key mt table table key */ lua_insert(L,-2); /* stack: obj key mt table key table */ storeatubox(L,1); /* stack: obj key mt table */ return 1; } } } lua_settop(L,3); } lua_pushnil(L); return 1; } else if (t== LUA_TTABLE) { lua_pushvalue(L,1); class_table_get_index(L); return 1; } lua_pushnil(L); return 1; }
不要被长段代码吓到,其实我们关注的只有这些代码
if (t == LUA_TUSERDATA)
{
/* Access alternative table */
#ifdef LUA_VERSION_NUM /* new macro on version 5.1 */
lua_getfenv(L,1);
if (!lua_rawequal(L, -1, TOLUA_NOPEER)) {
lua_pushvalue(L, 2); /* key */
lua_gettable(L, -2); /* on lua 5.1, we trade the "tolua_peers" lookup for a gettable call */
if (!lua_isnil(L, -1))
return 1;
};
其实这里就很明显了,就是拿下这个 userdata 对应的 env 表。然后如果没有设置过 peer 表,此时的 env 就是 TOLUA_NOPEER,TOLUA_NOPEER 其实就是 LUA_REGISTRYINDEX
#define TOLUA_NOPEER LUA_REGISTRYINDEX /* for lua 5.1 */
然后在这个 env 表中索引。注意,lua_gettable 是会触发元方法的,比如__index。这也就是为什么我们会在前面的 lua 代码中看到我们的 peer 上不会直接设置属性,而是给他设置原表通过__index 触发。
同样的,__newindex 也是同样的套路,这里就不展开说明了,读者可以自行考究。
tolua 中的 peer 函数
最后,为了证明我们的想法,我们看下 tolua 中的 getpeer 和 setpeer,来佐证我们的想法。
#ifdef LUA_VERSION_NUM /* lua 5.1 */ tolua_function(L, "setpeer", tolua_bnd_setpeer); tolua_function(L, "getpeer", tolua_bnd_getpeer); #endif
setpeer 的实现
static int tolua_bnd_setpeer(lua_State* L) { /* stack: userdata, table */ if (!lua_isuserdata(L, -2)) { lua_pushstring(L, "Invalid argument #1 to setpeer: userdata expected."); lua_error(L); }; if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_pushvalue(L, TOLUA_NOPEER); }; lua_setfenv(L, -2); return 0; };
getpeer 的实现
static int tolua_bnd_getpeer(lua_State* L) { /* stack: userdata */ lua_getfenv(L, -1); if (lua_rawequal(L, -1, TOLUA_NOPEER)) { lua_pop(L, 1); lua_pushnil(L); }; return 1; };
实现与我们前面的观察基本一致,也是利用了 fenv 来造一个 peer_table。当然这只是 lua5.1 中的实现,lua 后续的版本已经不用这套实现了,但是基本思路也大同小异,读者可以自行考究。
结
没想到一个简单的问题引起了这么多看代码的过程。纸上得来终觉浅,绝知此事要躬行。如果只是浅尝辄止地看了下大概的实现,可能这次看代码经历也就没什么意义了。希望以后遇到了一个看起来简单或者直觉型的问题能够有时间,也有心思去细细看一遍整个代码的流程。带着问题多看源码永远都是一个好处理方法。希望今后遇到问题能多看看源码,有时候也没有想象中那么复杂。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于