对于wxWidget的第三方库wxPropertyGrid,客户端代码实例化一个wxPropertyGrid时有时会发生CRASH。
简单的说,就是下面的代码行在某种情况下会CRASH:
wxPropertyGrid* pPG = new wxPropertyGrid(pParentWin, wxID_ANY);
那么到底是在哪种情况下呢?
编写了一个简单的测试代码,跟踪调试发现,CRASH时的callstack如下:
很明显的看出,此处的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属性,一步构造和两步构造都没有问题,但是要知道,如果一个窗口设置了这个属性,从它创建出来的所有子窗口都会有这个属性,当窗口布局很复杂时,有时候要定位问题会相当困难。