MFC No-No #1: Using
CWnd::GetDlgItem Incorrectly
(or, what the hell is a CTempWnd
object?)
By James R. Twine (Copyright 1998-2003, James R. Twine)
[Previous Article In Segment] [Next
Article In Segment]
Given the following classes:
class CBase
{
public:
/**/ CBase() {};
void BaseFunc() {};
};
class CDerived : public
CBase
{
public:
/**/ CDerived() {};
void DerivedFunc() { m_iDerivedValue = 0xFFFFFFFF };
int m_iDerivedValue;
}
Would you ever write code like this:
void CallDerived( CBase *pBase )
{
CDerived *pDerived = (CDerived*)pBase;
pDerived -> DerivedFunc();
return;
}
Of course not. And why not? Because you have not guaranteed
that the CBase pointer you
received is REALLY a CDerived
pointer before you casted it into one. If the
CBase object you were passed was
never a CDerived object in the first place, the call to
CDerived::DerivedFunc would
likely corrupt memory or crash because it tries to manipulate the memory of a
variable (CDerived::m_iDerivedValue)
that never existed!
However, in the world of MFC, it is very common to see something like:
void OnButtonPushed()
{
CEdit *pEdit = (CEdit*)GetDlgItem( IDC_EDIT );
pEdit ->SetWindowText( "Button Pushed!" );
return;
}
So what is the deal? The problem here is more evidence of the "it
works, so it must be correct!" syndrome. Basically,
whenever you do this with a CWnd-derived
class, you are basically getting lucky. Functions like
SetWindowText(...) do not
manipulate the state (data members) of the object, and are actually
just inline wrappers for a call to
SendMessage(...) with the appropriate parameters.
Want proof that the pointer returned by
GetDlgItem(...) is not a "real"
CEdit? QuickWatch it and
see what kind of instance it points to. Or, turn on RTTI and
try this:
void OnButtonPushed()
{
CEdit *pEdit = NULL;
CWnd *pWnd = GetDlgItem( IDC_EDIT );
pEdit = dynamic_cast< CEdit* >( pWnd );
return;
}
-And what do you think the value of
pEdit is going to be after the use of the
dynamic_cast operator?
If you guessed NULL, you are
correct! I am sure you can figure out why...! :)
Actually, the above code may return a "real"
CEdit object under certain
situations. When you call GetDlgItem(...),
MFC searches the handle map (for the current thread) for an existing object that
is bound to the handle in question (in this case, the
HWND of the control whose ID was
passed to the GetDlgItem(...)
function).
If one is not found, a temporary object is created (which in the case of
HWNDs is a
CTempWnd object), and this object
is returned. If an object is already bound to it, a pointer to the
existing object is returned. This temporary (heap allocated)
CTempWnd object is cleaned up
when the message pump starts pumping again (basically when you return from
whatever function you are in).
This means that if you had already bound a
CEdit class to the IDC_EDIT
control (like via ClassWizard), and then tried the above code that uses
GetDlgItem(...), you would get
back a a pointer to a "real" CEdit
object, the one that is already bound to the control.
The fact that GetDlgItem(...)
can return a pointer to a temporary object raises one important tip: Do not
store the pointer returned from GetDlgItem(...)
in anything like a member variable or some other variable that may be used at a
later time: by the time something tries to use that value, the object may have
already been cleaned up.
[Previous Article In Segment] [Next
Article In Segment]
|