先说一下项目,用CEGUI做界面,然后由tolua++导出各种函数给lua使用。
//基类
namespace CEGUI {
class Window{
...
void setText(const String&text, COLOR c=COLOR_BLACK);
}; // end of Window
//StaticText类
class StaticText:public Window
{
}
//ItemCell类
class ItemCell:public Window
{
...
void setText(const String& text); //实际上此方法会隐藏基类方法
}
...
} //end namespace CEGUI
// lua代码这样写
local win = WindowManager:getWindow("xxx")
win:setText("text message", COLOR_RED);
一般来讲getWindow返回类型是"CEGUI::Window",但是我们发现在win:setText这行代码,偶发性会报错,概率比较低:
error in function "setText", argument #3 is number; "[no object] expected".
尝试把COLOR_RED参数去掉就不报错了。
把win类型用tolua.type(win)打印出来,发现报错的时候类型不是"CEGUI::Window",而是"CEGUI::ItemCell",这就很奇怪了,为什么类型会变呢?
首先怀疑是CEGUI的bug,尝试把xxx.layout简化,只保留一个StaticText控件,问题依旧存在,基本排除CEGUI出bug的可能性。
查看 tolua++ 依据pkg文件生成的中间文件,发现调用getWindow的地方是这么写:
CEGUI::Window* tolua_ret = (CEGUI::Window*) Mtolua_new((CEGUI::Window)());
tolua_pushusertype(tolua_S,(void*)tolua_ret,"CEGUI::Window"); //这是一个c++方法
查阅tolua++相关资料,tolua_pushusertype主要作用就是构造一个给lua用的userdata对象,大概的逻辑是
将c++对象指针作为key, 类型作为value存储于tolua_ubox表(当然这个表里还有userdata)。若key不存在直接插入即可;若存在则根据继承关系判断是否满足条件,若满足则不做任何处理直接返回此userdata对象,若不满足则修订value类型。
lua伪代码:
function tolua_pushusertype(luaState, pVoid, type)
if not tolua_ubox[pVoid] then
tolua_ubox[pVoid] = type
.. create userdata and return ...
else
if type_compatible(tolua_ubox[pVoid], type) then
.. find userdata and return ...
end
tolua_ubox[pVoid] = type
.. update userdata and return ...
end
end
下面说下type_compatible逻辑, 如果参数是以下几种情况则返回true,否则返回false
1.ChildClass, BaseClass
2. Type, const Type // tolua++ 认为 Type类型可以当做const Type使用,但不能反过来
好了,解释下最开始遇到的问题,
1.创建了一个 CEGUI::ItemCell对象,返回CEGUI::Window类型,然后转换到 CEGUI::ItemCell类型, 指针地址为p, 然后销毁了, 但是lua并没有立即销毁对应的userdata,而是走自己的gc逻辑
2.创建了一个CEGUI::StaticText对象, 刚好指针也是p,返回userdata对象的时候经过tolua_pushusertype的逻辑处理,类型就变成了 CEGUI::ItemCell类型。
怎么验证这个说法?
这里有一个例子: http://git.oschina.net/kylescript/bug_of_tolua
怎么修复这个问题呢?
打开文件 tolua_push.c // tolua++ 1.0.93 或者 1.0.92
注释掉 98-103行
/* 这段代码 与 我们的伪代码type_compatible返回true逻辑是一样的
if (lua_toboolean(L,-1) == 1) /* if true */
{
lua_pop(L,3); /* mt ubox[u]*/
lua_remove(L, -2);
return;
}
*/
这样就修复掉这个问题了,每次都是修订为参数传的type类型,不再判断继承关系啦。
但是,这样修改真的没问题吗? 为什么作者要设计这个判断类型关系多次一举呢?
注释掉这5行后新问题出现了。
假如有一个类是这样写的:
class ProblemClass{
public:
...
const ProblemClass & func(...)
{
...
return *this;
}
void change_index(int i) {
_index = i;
}
private:
int _index;
};
把这个类导入 pkg 文件, 并导出func、change_index方法,我们这样写lua代码
local pc = ProblemClass:new_local()
...
pc:func(...)
pc:change_Index(0) //这行会出错
报错:error in function "change_index", argument #1 is "const Problem"; "Problem" expected".
什么? 怎么变成const类型了?
原因就是func函数,返回了一个*this, 这个*this地址和pc userdata对应的c++对象地址是一样的,被直接修改掉了!
那问题应该怎么解决或者规避呢?我大概想了两个方案
1. 不修改tolua++,删除ItemCell子类的setText方法,走父类的setText逻辑,或者子类setText干脆换个名字。 (这,这真有点耍流氓的赶脚~~~)
2. 注释掉那5行,,不再判断继承关系。 同时不允许导出类似 const Type& func 函数。
或许,大概,也许,这就是tolua++设计这个继承关系检测的初衷?!怎么看都像是设计缺陷啊~~