C++对象绑定到lua通常是创建一个userdata用来存放cpp对象。在tolua库中维护了一张表:tolua_ubox表,它是个弱表,用来缓存所有cppobj的ptr->userdata映射。
userdata对象的生成过程和cpp对象到userdata的映射的生成。
void tolua_pushusertype_internal (lua_State* L, void* value, const char* type, int addToRoot)
{
if (value == NULL)
lua_pushnil(L);
else
{
luaL_getmetatable(L, type); /* stack: mt */
if (lua_isnil(L, -1)) { /* NOT FOUND metatable */
lua_pop(L, 1);
return;
}
lua_pushstring(L,"tolua_ubox");
lua_rawget(L,-2); /* stack: mt ubox */
//现在每个metatable都维护了一份tolua_ubox表,在类的生成过程,就会为metatable生成这个tolua_ubox表,具体请看mapinheritance和set_ubox
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_pushstring(L, "tolua_ubox");
lua_rawget(L, LUA_REGISTRYINDEX); 在创建tolua环境就会创建注册表中的tolua_ubox表
};
lua_pushlightuserdata(L,value); /* stack: mt ubox key
lua_rawget(L,-2); /* stack: mt ubox ubox[value] */
//查看在mt中的ubox表中是否存在value(C++对象),如果不存在,创建一个,并设置值为userdata,值为value。
if (lua_isnil(L,-1))
{
lua_pop(L,1); /* stack: mt ubox */
lua_pushlightuserdata(L,value);
*(void**)lua_newuserdata(L,sizeof(void *)) = value; /* stack: mt ubox value newud */
//newud就是userdata
lua_pushvalue(L,-1); /* stack: mt ubox value newud newud */
lua_insert(L,-4); /* stack: mt newud ubox value newud */
lua_rawset(L,-3); /* ubox[value] = newud, stack: mt newud ubox */ 映射
lua_pop(L,1); /* stack: mt newud */
/*luaL_getmetatable(L,type);*/
lua_pushvalue(L, -2); /* stack: mt newud mt */
lua_setmetatable(L,-2); /* update mt, stack: mt newud */ 设置userdata的metatable为cpp的metatable。
#ifdef LUA_VERSION_NUM
lua_pushvalue(L, TOLUA_NOPEER); /* stack: mt newud peer */
lua_setfenv(L, -2); /* stack: mt newud */ 设置userdata的环境为TOLUA_NOPEER
#endif
}
else
{
//.....
}
}
那这和C++对象使用lua对象的函数,变量有什么关系呢?
其实一直在纠结lua对象继承C++对象的这个问题,到今天才看到比较满意的解决办法。
function cls.extend(target, ...) //target是个C++对象来的
-- 先继承C++对象
local t = tolua.getpeer(target)
if not t then
t = {}
tolua.setpeer(target, t)
end
setmetatable(t, cls)
cls.ctor(target, ...)
return target
end
在网上其实存在很多这个例子。
知其然不知其然怎么行呢?
tolua_getpeer和tolua_setpeer是什么鬼:
static int tolua_bnd_getpeer(lua_State* L) {
/* stack: userdata */
lua_getfenv(L, -1);
if (lua_rawequal(L, -1, TOLUA_NOPEER)) { 在上面的描述中,userdata的环境就是TOLUA_NOPEER,所以这边会返回空
lua_pop(L, 1);
lua_pushnil(L);
};
return 1;
};
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); 为userdata设置新的环境
return 0;
};
所以例子中的
local t = tolua.getpeer(target)
if not t then
t = {}
tolua.setpeer(target, t)
end
这段是为了给这个C++对象(userdata)设置一个新的环境。这个环境现在什么都没有
setmetatable(t, cls) 现在这个环境中有了cls,这个cls就是Lua table。可以看做是我们的lua对象,lua class
然后返回C++对象。这样就可以通过C++对象使用到lua对象中的所有内容。其实在lua文件中可以创建这样的C++对象,既可以使用这个对象原有的东西还可以使用我们的lua内容,其实换个思路想想,如果把返回的对象看做是lua对象,这也就是lua对象继承C++对象嘛。
范例:
local tt = cc.Node:create()
local env = {}
env.abc = 4
env.func = function()
print("环境函数") //输出 环境函数
end
local peer = tolua.getpeer(tt)
if not peer then
tolua.setpeer(tt, env)
print(tt.abc) //输出4
tt.func()
end