[tolua++] 永远不要用tolua.type

来源:互联网 发布:买下跳跃网络要多少钱 编辑:程序博客网 时间:2024/06/15 05:07

先说一下项目,用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] thentolua_ubox[pVoid] = type.. create userdata and return ...elseif type_compatible(tolua_ubox[pVoid], type) then.. find userdata and return ...endtolua_ubox[pVoid] = type.. update userdata and return ...endend

下面说下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++设计这个继承关系检测的初衷?!怎么看都像是设计缺陷啊~~

0 0
原创粉丝点击