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