Attaching and Detaching Objects

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...

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Before software should be reusable, it should be usable.” - Ralph Johnson