Click here to Skip to main content
Click here to Skip to main content
Go to top

Attaching and Detaching Objects

, 16 May 2000
Rate this:
Please Sign up or sign in to vote.
Attaching and detaching MFC objects to and from Windows objects.

Introduction

MFC provides a set of "wrapper objects" that contain embedded Windows objects. For example, a CWnd wraps an HWND, a CFont wraps an HFONT, a CBrush wraps an HBRUSH, and so on. These are summarized in the table below. There are some significant interactions between the MFC and Windows objects, which you need to understand.

Failure to deal with these issues can lead to problems with spontaneous ASSERT statements, access errors, disappearing objects, and other, more subtle problems.

This essay attempts to elucidate the issues of dealing with the MFC/Windows interface.

MFC Ojbect Windows Object (variant)
CWnd HWND any window
CEdit HWND EDIT
CListBox HWND LISTBOX
CStatic HWND STATIC
CComboBox HWND COMBOBOX
CGDIObject (gdi object) (any)
CBitmap HBITMAP  
CBrush HBRUSH  
CPen HPEN  
CFont HFONT  
CRegion HRGN  
CPalette HPALETTE  


Creation: The Windows Two-Step

Creation of most objects involves a two-step process. The first step is to create a C++ object, which is the "wrapper" around the Windows object. The next step is to create the actual Windows object. Some parameterized constructors do both of these steps at once. For example,

CPen pen;

Creates an MFC object, a CPen, but does not associate an HPEN with it. But the constructor

CPen pen(PS_SOLID, 0, RGB(255, 0, 0));

creates an MFC object, a CPen, then creates the underlying Windows object, the HPEN, and attaches that object to the CPen.

You can do this implicitly, by using the Create method (which is sometimes gratuitously renamed, as far as I can tell because the designers of MFC were not C++ experts). For example, to create a pen you can do

CPen pen;
pen.CreatePen(PS_SOLID, 0, RGB(255, 0, 0));

(MFC has CreatePen and CreatePenIndirect, which is silly, because there is no Create method in either the CPen or the CGDIObject superclass).


Wrapper classes and 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).-->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...


Dissociating wrappers and objects: Detach()

One solution often used by programmers is the following:

{
    CFont * f;
    f = new CFont;
    f->CreateFont(...);
    c_EditControl.SetFont(f);
}

Empirical observation demonstrates that this code works, and works correctly. This is true. It works. But it is sloppy code. What, exactly, happened to that CFont object referenced via the CFont *? Nothing, that's what happened. There is a rogue CFont out there, unreachable, and undestroyable. It will stay around forever. This might be harmless, but it is not good programming practice.

What I do is as follows, and this is a very important trick when you are using MFC at the MFC-to-Windows interface. I use the Detach method:

{
    CFont f;
    f.CreateFont(...);
    c_InputData.SetFont(&f);
    f.Detach(); // VERY IMPORTANT!!!
}

The Detach operation dissociates the Windows object from its wrapper, and returns as its value the underlying Windows object handle. Since I don't need it, I don't bother to assign it to anything. But now, when the CFont is destroyed, the associated m_hObject handle is NULL and the underlying HFONT is not destroyed.

If you were to read my code, you'd find lots of instances of Detach inside. An example is my <!---->window bitmap capture function<!---->, which gets a bitmap for a specified window and puts it in the clipboard. In order to keep the bitmap from being destroyed on scope exit, it detaches the bitmap from the CBitmap object.


When Detach is not good enough: CDialog and modeless dialogs

One of the common questions in the various forums is along the lines of "I tried to create my modeless dialog, and it failed. I never get the dialog. What's wrong?" and it is accompanied by a snippet of code that looks like this:

void CMyClass::OnLaunchDialog()
{
     CMyToolbox dlg;
     dlg.Create(dlg.IDD);
}

At this point you should now understand what has happened. The dialog is created, but as soon as the scope containing the variable is exited, the destructor comes in, and since this is a window, calls DestroyWindow on the associated object. Of course, this technique will always work for a modal dialog, as in

{
    CMyDataEntryScreen dlg;
    dlg.DoModal();
}

because after the modal dialog exits there is no need for the dlg variable. 

(It has never made sense to me why I have to provide the dialog ID for the Create method, since it is implicit in the class!)

But you can't use Detach here, because the dialog wants to process messages and it wants to have state. I've never tested this, but I suspect that if you actually did do a Detach that you would either start getting massive numbers of ASSERT failures, or you would experience an access fault of some sort. You really need that modeless dialog object around!

This is a case where the correct method is to create a CDialog reference, for example, add the following line to your CWinApp class (this assumes you only want one toolbox for all instances of windows):

// in your CWinApp class
CMyToolbox * tools;

// in your CWinApp constructor:
tools = NULL;

// in your activator:
void CWinApp::OnOpenToolbox()
{
    if(tools != NULL)
    { /* use existing */
        if(tools->IsIconic())
            tools->ShowWindow(SW_RESTORE;
        tools->SetForegroundWindow();
        return;
    } /* use existing */
    tools = new CMyToolbox;
    tools->Create(CMyToolBox::IDD);
}

This will create the window if it does not exist, and if it does, it simply brings it up to the top. This code assumes that the window can be minimized, so it will restore it if necessary. If you don't support minimization of the dialog, you may not need the SW_RESTORE operation. OTOH, it is sometimes convenient to not actually destroy the window when it is "closed", but simply to hide it, in which case you would use SW_SHOW as the operation.

Why not just create a CMyToolbox variable (not a reference)? Well, because you need to know when to delete the object, and it is easier if you are totally consistent and never allocate one on the stack or as a class member, but only use pointers to a heap-allocated version. You need to add a PostNcDestroy handler, which is a virtual method, to your class, of the form:

void CMyToolbox::PostNcDestroy()
{
    CDialog::PostNcDestroy();
    // add this line
    delete this;
}

This guarantees that when the window is closed, the instance of the window will be deleted. Note that this does not change your pointer, in our example, tools, so unless you explicitly set it to NULL you are going to be in deep trouble!

I handle this in a variety of ways. The most common method I use is to post a user-defined message back to the parent window that the modeless dialog has been destroyed. This tells the CWinApp class that it can zero out the variable. Note that a CWinApp, although a CCmdTarget, is not a CWnd, so you can't post a message to it using PostMessage. Instead, you have to do PostThreadMessage and do an ON_THREAD_MESSAGE or ON_REGISTERED_THREAD_MESSAGE to handle it. If you don't know what I'm talking about, read my <!---->essay on message management<!---->.

void CMyToolbox::PostNcDestroy()
{
    CDialog::PostNcDestroy();
    // add these lines
    delete this;
    AfxGetApp()->PostThreadMessage(UWM_TOOLBOX_CLOSED, 0, 0);
}

And in the class definition of your CWinApp class, add

afx_msg LRESULT OnToolboxClosed(WPARAM, LPARAM);

and add in your CWinApp message map:

ON_THREAD_MESSAGE(UWM_TOOLBOX_CLOSED, OnToolboxClosed)

or

ON_REGISTERED_THREAD_MESSAGE(UWM_TOOLBOX_CLOSED, OnToolboxClosed)

and the implementation method

LRESULT CMyApp::OnToolboxClosed(WPARAM, LPARAM)
{
    tools = NULL;
    return 0;
}

I explain the difference between ordinary messages and registered messages in my <!---->essay on message management<!---->. There are some hazards with this, because if your application happens to be in a message loop other than the main message pump (for example, has a modal dialog or MessageBox active) the PostThreadMessage won't be seen, and you have to handle a PostMessage in the MainFrame class.


Optimization and Correctness

Back in the days of Win16, GDI resources were scarce and precious. They were carefully hoarded. Programmers went to a lot of extremes to avoid running out of GDI resources. This meant that if you ever created a font, you created it once and used it as often as possible, and deleted it when your program terminated.

This was a great optimization, and it was necessary then. It is not really a good idea in modern Win32 systems. The reason is that it violates abstraction. If I create a control that expects a special font, I should create that font for that control, and not require the programmer create it for me. The CFont object is therefore in the control subclass, not a global variable or a variable of the CDialog class or CWinApp class. If all instances of the control share the same font requirement, you can simplify things a bit by using a static class member to hold the CFont, although you will probably have to reference-count it to know when to delete it.

An optimization which renders a program incorrect, or potentially incorrect, is not an optimization. Beware! Because the consequence of some optimizations to "save space" actually can leak space. This Is Not Good


Changing Fonts in a Control

Changing a font in a control requires that you delete the existing font. This is why it is a Good Idea to have a font used in only one control, and not shared. The following code is correct, and does not leak fonts everywhere:

void CMyControl::ChangeFont()
{
    CFont f;
    f.CreateFont(...);
    CFont * oldfont = GetFont();
    if(oldfont != NULL)
        oldfont->DeleteObject(); // Be Careful! See discussion <A href="#A caveat of the above code">below</A>!
    SetFont(&f);
    f.Detach();
}

This has the advantage that it doesn't leak HFONTs each time the font is changed. It is left as an Exercise For The Reader to figure out how the parameters to CreateFont are specified; I find that I usually provide as parameters to my ChangeFont method the size, the face name, and a bold flag, and that encompasses 99% of what I do in changing fonts in controls. In generalizing the above example, you have to figure out what your needs are.

The GetFont returns a reference to a CFont object, unless there was no font associated with the control, in which case the result is NULL. But if the result is non-NULL, I delete the HFONT by calling DeleteObject. If the font was one of mine, it is now deleted (and if it was shared, the other users of the font are in trouble--but we'll get to that shortly). If the font was a stock font, the DeleteObject has no effect. The SetFont then sets the font, and the Detach, as I just explained, keeps the HFONT from following the CFont into the Great Bit Bucket In The Sky.

Whoa! What about that CFont * we got? Aren't we leaking something there? No, because the CFont * that was created by GetFont is a temporary MFC Object, which means that it is added to a list of garbage-collectable objects. The next time there is some idle time, the default OnIdle method goes around and cleans up all the temporary objects by deleting them. In fact, if you say "delete oldfont" you will eventually get an ASSERT failure from the temporary-object collector, which tells you that you've Done Something Bad to its data.


A caveat of the above code

There's a bug in the above code. It is not an apparent bug, but a reader of this essay found it and pointed it out to me. I need to be more explicit here. You should do DeleteObject only to fonts you have created. The situation was this: the reader did exactly what I showed, and complained that although his buttons how had the desired font, all the other buttons now had the wrong font. Whoops. This was my error. What happens in a dialog is that a font is created for the controls in the dialog, and a SetFont is done for each control as it is created.

What had happened was that by following my advice, he managed to delete the dialog font, so all the other controls fell into the default case. The proper specification is that you should delete the old font only for fonts you have created, and not for the default font of a control. How can you tell the difference? Well, if you are in the OnInitDialog handler, you know you didn't create the font, so you shouldn't delete the existing font. I have not checked this out, but I believe the dialog will delete its copy of this font when it is closed.

What if you are changing the font later, and don't know if you created the font the last time or not? Well, the obvious way is to keep some sort of Boolean around saying whether or not you changed the font. A typical case might be when you are using a CListBox as a logging control, and want the user to be able to change the font. This is needlessly complex. When I've had to do this (note that I already knew better when I wrote the example!1 What I did was do a GetFont for the existing control. Then I created a new font which was identical to it and set that font in the control. The code to do this is shown below:

// Create a new font so we can change it later
CFont * f = c_MyLogListBox.GetFont();
CFont newfont;
LOGFONT lf;
if(f != NULL)
{ /* Set up duplicate font */
    f->GetObject(sizeof(LOGFONT), &lf);
    newfont.CreateFontIndirect(&lf);
} /* Set up duplicate font */
else
{ /* Use default font spec */
    newfont.CreateStockObject(ANSI_VAR_FONT);
} /* Use default font spec */
c_MyLogListBox.SetFont(newfont);
newfont.Detach();

After you do this, you can safely use my previous example to delete the previous font because you know it is one that you created yourself.

Thanks, and a wave of the Flounder Fin to Michael Mueller for taking the time to point out this problem.


Attaching handles to wrappers: Attach()

There is a corresponding operation to Detach, which is Attach. The Attach operation takes a HANDLE argument of the appropriate type and attaches that handle to an existing MFC object. The MFC object must not already have a handle attached.

Thus, if I buy a third-party DLL that tells me that one of its functions returns an HFONT, or an HWND, or an HPEN, or any other handle, I can attach that object to a corresponding already-existing MFC object by using Attach. Consider that I've got a DLL which has an operation getCoolPen for the operations it wants to do. It returns an HPEN, which I can store. But it may be convenient for me to store that as an MFC object. One way to do this is to declare, for example in a CView-derived class, a member variable (probably a protected member variable),

CPen myCoolPen;

I can then do something like

void CMyView::OnInitialUpdate()
{
    // ...
    myCoolPen.Attach(getCoolPen());
    // ...
}

Note that this requires that you understand the implications of the getCoolPen call. If the DLL writer has properly documented the product, it will state explicitly if you must delete the HPEN when you are done with it, or must not delete the HPEN because it is shared. Often such useful information is only determinable by reading the sides of airborne porcine creatures. But let's assume that we actually know what should be done.

In the case where you must delete the HPEN when you are no longer interested in it, you don't need to do anything special. When the view is destroyed, the destructors for all its members are called, which means the CPen destructor is called, deleting the underlying HPEN.

In the case where you must not delete the HPEN because it is shared, you must add to the destructor for your CView-derived class a line like the one shown below:

CMyView::~CMyView()
{
    // ...
    myCoolPen.Detach();
    // ...
}

Creating Objects: FromHandle

All the wrapper classes support one additional operation, the FromHandle method. This is a static method of the wrapper class, and it takes as an input argument a handle of the underlying Windows object, and returns as a result a temporary wrapper object. A permanent object is one which will not be garbage-collected during idle time.

Thus, if I simply do a GetFont, I get a reference to a temporary object. This pointer cannot be stored, because eventually the space it occupies will be reclaimed. The following code is fatally flawed:

class CMyClass : public CView {
protected:
    CFont * myFont;
};

// anywhere in the class implementation
myFont = GetFont();
// or
myFont = CFont::FromHandle(...);

An attempt to use the variable myFont at any later time has an excellent chance of failing in a suitably interesting catastrophic way. Perhaps an ASSERT failure, or an access fault, or simply incorrect behavior, such as no apparent font change. This is because the object was created by GetFont, added to the list of temporary objects, and later deleted. When a temporary object is deleted, the underlying Windows object is not deleted as the temporary object is seen as only a proxy.

The correct way to store a reference to an underlying object is as follows:

CFont * f = GetFont();
if(f == NULL)
    myFont = NULL;
else
{ /* attach it */
    myFont = new CFont;
    myFont->Attach(f->m_hObject);
} /* attach it */

Note that this presumes that myFont is either NULL or its value is meaningless. If it is non-NULL, there is an excellent chance that it was already holding a valid CFont reference. You have to decide if you should delete that reference, and if you delete that reference, what should happen to the underlying HFONT. You can only do this if the variable myFont is not already holding a reference to a temporary object. In the above example, since I create a new CFont each time, I know it is not a temporary object. Two possible algorithms are:

if(myFont != NULL)
    delete myFont; // delete object and HFONT

or, alternatively

if(myFont != NULL)
{
    myFont->Detach(); // leave HFONT alone!
    delete myFont;
    myFont = NULL;
}

Don't forget to set the myFont member to NULL in the class's constructor!

If you should happen to delete an object which is already a temporary object, you will get an assertion failure or possibly even an access fault from deep inside MFC when it tries to delete the temporary object that had been allocated. Never delete temporary objects. Thus the following code is fatal:

CFont  * f;
f = somewindow.GetFont();
delete f;

If you get this, you will know almost immediately that you have deleted a temporary object; shortly after you return to the main message loop, you will get an assert failure.


Windows: FromHandle and FromHandlePermanent

For CWnd-derived classes, you can get a temporary CWnd * object from FromHandle. Thus, if you have a class CMyCoolWindow and you do something like GetFocus you may or may not get an actual pointer to your CMyCoolWindow * object. I've had numerous failures in this way. For example, I've taken to doing

CWnd * capture = GetCapture();
if(capture != NULL && capture->m_hWnd == m_hWnd)
{ /* I have capture */
    // ... do something
} /* I have capture */

If you need a handle to your actual object for a given HWND, you should use CWnd::FromHandlePermanent. This will return a handle from the permanent window map. Note that CWnd::FromHandle might return a handle to a permanent window, and then again, it might not. You have no guarantee.


CWnds and Threads

The object maps are thread-local. This means that if you are in a thread and do a CWnd::FromHandle you will get a new, temporary window object which is not the same C++ object that represented your class initially. Thus this is always fatal in a thread:

CMyCoolWindowClass * me = (CMyCoolWindowClass *)CWnd::FromHandle(hWnd);
me->MyCoolVariable = 17; // modifies some location you've never heard of!

You will actually get a generic CWnd pointer, and if you did

<FONT face=Courier>me->IsKindOf(RUNTIME_CLASS(CMyCoolWindowClass))</FONT>

you would get FALSE. If you did

CMyCoolWindowClass * me = (CMyCoolWindowClass *)CWnd::FromHandlePermanent(hWnd);

you would always get NULL because the permanent handle map for the thread is empty, unless you actually created the window in that UI-thread.

If you need access to a window class in a thread, particularly in a worker thread, pass it into the thread via the thread routine's initial pointer. See my essay on <!---->Worker Threads<!----> for more details.


Summary

While this essay has concentrated primarily on the CFont object, the techniques here apply to all MFC classes that are wrappers for Windows objects. CGDIObject, the superclass of CPen, CBrush, CFont, CRgn, CPalette, and others is where Attach, Detach, and FromHandle are implemented. Subclasses such as CPen override FromHandle to take an HPEN and return a CPen *, but in fact they simply call the superclass to do all the work and provide the type casting necessary to make things work right in a C++ environment. In addition, the CWnd class has Attach, Detach, and FromHandle. The CWnd class has one other operation, FromHandlePermanent, which I may someday write about, but not right now.

All of these operations are designed to allow you to move freely between the Windows object domain, where objects are represented by instances of HANDLEs, and the MFC object domain, where objects are represented by class instances of C++ classes. It can help you a lot to understand the relationship between these two representations, and how to use them in a safe and non-leaking fashion.



A footnote: (return to text)

1 OK, I can't resist it. I tell this story every time I teach a class, because it captures one of the problems of communicating knowledge.

Many years ago, in an arbitrarily chosen Oriental culture, the young student comes to the great martial arts Master and says he wishes to become his acolyte. "It is not an easy choice you make, child; you will have to study many years to become a great Master". The young student is persistent, and says he understands the commitment, and ends up spending the next 20 years learning to be a Master. Finally, the great Master says to him "My Son, you now know all I know. Go forth and take a student of your own". At that point, the student thinks, Aha! I know everything he does. I know every move and countermove! I can take him!

The student wakes up several hours later. The Great Master is looking down on him, shaking his head, and saying "Ahh, forgot that trick!"       (return to text)



The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this article.
Copyright © 1999 <!--webbot bot="Substitution" s-variable="CompanyLongName" startspan -->CompanyLongName<!--webbot bot="Substitution" endspan i-checksum="22147" --> All Rights Reserved.
http://www.flounder.com/mvp_tips.htm

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Joseph M. Newcomer

United States United States
No Biography provided

Comments and Discussions

 
QuestionChanging a font in a control requires that you delete the existing font??? Pinmemberflyingxu9-Aug-07 20:52 
AnswerRe: Changing a font in a control requires that you delete the existing font??? PinmemberJoseph M. Newcomer10-Aug-07 10:42 
GeneralKudos to you PinmemberBriball28-Oct-04 12:13 
Generalproblem with controlling listbox from two threads Pinmembernnnnnnn26-Jan-04 9:57 
GeneralStory PinmemberAnthony_Yio20-Nov-02 22:40 
GeneralThis is great! PinsussCuong Cao21-May-00 12:08 
GeneralMenu PinsussWagner Santos18-May-00 1:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140921.1 | Last Updated 17 May 2000
Article Copyright 2000 by Joseph M. Newcomer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid