[Effective WX] wxPropertyGrid遇上wxWS_EX_VALIDATE_RECURSIVELY产生的BUG

对于wxWidget的第三方库wxPropertyGrid,客户端代码实例化一个wxPropertyGrid时有时会发生CRASH。

简单的说,就是下面的代码行在某种情况下会CRASH:

wxPropertyGrid* pPG = new wxPropertyGrid(pParentWin, wxID_ANY);

那么到底是在哪种情况下呢?

编写了一个简单的测试代码,跟踪调试发现,CRASH时的callstack如下:

[Effective WX] wxPropertyGrid遇上wxWS_EX_VALIDATE_RECURSIVELY产生的BUG_第1张图片

很明显的看出,此处的m_tlp是一个未被正确初始化的值那么为什么会这样呢? 我们一起跟踪源代码来看下到底是怎么回事。


第一个疑问就是在构造一个wxPropertyGrid时,为啥会调用到OnTLPChanging函数?

从上面bt的结果来看,wxPropertyGrid::SetExtraStyle函数触发了它的调用,而SetExtraStyle函数是被窗口基类的CreateBase调用的,那么可以知道SetExtraStyle是一个虚函数,并且wxPropertyGrid对它进行了重写。翻看wxPropertyGrid的源代码,的确是这样(@line4006):

void wxPropertyGrid::SetExtraStyle( long exStyle )
{
    if ( exStyle & wxPG_EX_DISABLE_TLP_TRACKING )
        OnTLPChanging(NULL);
    else
        OnTLPChanging(::wxGetTopLevelParent(this));

    if ( exStyle & wxPG_EX_NATIVE_DOUBLE_BUFFERING )
    {
我们代码中并没有增加wxPG_EX_DISABLE_TLP_TRACKING属性。

SetExtraStyle在哪被调用的? 如下代码:

269     if ( parent && (parent->GetExtraStyle() & wxWS_EX_VALIDATE_RECURSIVELY) )
270     {
271         SetExtraStyle(GetExtraStyle() | wxWS_EX_VALIDATE_RECURSIVELY);
272     }
这里的parent应该就是实例化wxPropertyGrid时,传入的pParentWin。如果这个父窗口设置了wxWS_EX_VALIDATE_RECURSIVELY属性,那么它会自动对其所有子窗口设置这个属性。

构造wxPropertyGrid时,调用它的SetExtraStyle函数之前,并非所有的成员变量都进行了初始化,就比如这里的m_tlp,为什么会这样呢?还是来看源代码,看看它的构造函数都干了些什么:

wxPropertyGrid::wxPropertyGrid ( wxWindow * parent,
                                wxWindowID id ,
                                const wxPoint & pos,
                                const wxSize & size,
                                long style ,
                                const wxString & name )
    : wxScrolledWindow()
{
    Init1();
    Create(parent ,id, pos,size ,style, name);
}
bool wxPropertyGrid ::Create( wxWindow *parent ,
                             wxWindowID id ,
                             const wxPoint & pos,
                             const wxSize & size,
                             long style ,
                             const wxString & name )
{
     // more here....

    wxScrolledWindow::Create (parent, id,pos ,size, style,name );

    Init2();

    return true ;
}
wxPropertyGrid有2个构造函数,一个是不带任何参数的默认构造函数,另一个就是带相当多参数的构造函数。这2种构造函数借口也是绝大多数wxWidget窗口类提供的通用的构造方式,前者是两步构造法,先调用默认构造函数,然后调用它的Create函数,后者是一步构造法。我们前者代码中是一步构造法。

一步构造法中,先调用Init1()函数,然后调用父类的Create函数,之后再调用Init2()。问题在于m_tlp变量没有在Init1()函数中初始化,而是在Init2()中,也就是父类的Create函数调用之后。这也就是在调用SetExtraStyle函数时,m_tlp变量并没有初始化的原因。


如何解决这个问题?

其实这个问题也好解决,就是将一步构造改成两步构造,并在两步之间对相关变量进行初始化,譬如m_tlp=NULL; 当然得能访问它的非公有成员变量,才能达到这个目的。从它的源代码可知,wxPropertyGrid大部分的数据成员都是protected的,所以可以通过对wxPropertyGrid类进行继承来访问它的protected成员。

当然如果父窗口如果没有设置wxWS_EX_VALIDATE_RECURSIVELY属性,一步构造和两步构造都没有问题,但是要知道,如果一个窗口设置了这个属性,从它创建出来的所有子窗口都会有这个属性,当窗口布局很复杂时,有时候要定位问题会相当困难。




你可能感兴趣的:([Effective WX] wxPropertyGrid遇上wxWS_EX_VALIDATE_RECURSIVELY产生的BUG)