Wrapper Classes & Objects
There is a serious implication to having a Windows object attached to an MFC
object. When the MFC object is destroyed, the attached Windows object is destroyed.
This has some serious implications. A common error that many programmers make
is this one:
{
CFont f;
f.CreateFont(...);
c_InputData.SetFont(&f);
}
They are surprised when there is apparently no effect on the control. Which
is pretty amazing, because they've done something like this before:
{
CFont f;
f.CreateStockObject(ANSI_FIXED_FONT);
c_DisplayData.SetFont(&f);
}
and it worked perfectly!
Actually, the second example worked no better than the first example. It succeeded
because of a special case they managed to take advantage of, unknowlingly.
What happens in the first example is that the CFont object is created
on the stack, as expected. The CreateFont with its tedious list of parameters
then creates an HFONT object, which is represented by its handle
value, and attaches the HFONT to the CFont. This is all good so
far. The method SetFont is called on the window reference c_InputData,
a CEdit control (if you don't know how to do this, read my essay
on avoiding GetDlgItem). This eventually generates a message to the
edit control, which we can simplify as shown below (you can go read the MFC code
if you want the real details).
void CWnd::SetFont(CFont * font)
{
::SendMessage(m_hWnd, WM_SETFONT, font->m_hObject, TRUE);
}
Note that what is sent to the control is the HFONT value. All is good
thus far.
Now we leave the block in which the variable was declared. The destructor,
CFont::~CFont is called. When the destructor for a wrapper is called the
associated Windows object is destroyed. The explanation of the destructor
can be simplified and illustrated as the following code (again, the truth is
somewhat more complex and you can read the MFC source yourself):
CFont::~CFont()
{
if(m_hObject != NULL)
{
::DeleteObject(m_hObject);
}
}
By the time the edit control gets around to painting itself, in its WM_PAINT
handler, it wants to select its associated font into its Display Context (DC).
You can imagine the code to be something of the form shown below. This is very
simplified code, and is meant to be indicative rather than definitive, and you
won't find it in the MFC source because it is part of the underlying Windows
implementation.
edit_OnPaint(HWND hWnd)
{
HDC dc;
PAINTSTRUCT ps;
HFONT font;
dc = BeginPaint(hWnd, &ps);
font = SendMessage(hWnd, WM_GETFONT, 0, 0);
SelectObject(dc, font);
TextOut(dc, ...);
}
Now look at what happens. The CFont was destroyed, which in turn destroyed
the HFONT. But the HFONT had already been passed in to the EDIT
control and is sitting there. When the SelectObject is done inside the
edit handler, it specifies an invalid handle, so the SelectObject is ignored.
Therefore, it appears that there is no change.
So why did this work when the ANSI_FIXED_FONT was selected? Well, stock
objects have special properties, and one of these special properties is that
DeleteObject is ignored for stock objects. So in fact the code was incorrect,
and worked only because the stock object is never actually deleted. (If you've
heard that you musn't delete stock objects, you either started as a Windows 3.0
(Win16) programmer or have been talking to someone who did. This bug was fixed
with the release of 16-bit Windows 3.1).
How do you get around this? Keep on reading...