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
. 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();
}
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):
CMyToolbox * tools;
tools = NULL;
void CWinApp::OnOpenToolbox()
{
if(tools != NULL)
{
if(tools->IsIconic())
tools->ShowWindow(SW_RESTORE;
tools->SetForegroundWindow();
return;
}
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();
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();
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();
SetFont(&f);
f.Detach();
}
This has the advantage that it doesn't leak HFONT
s 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.
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:
CFont * f = c_MyLogListBox.GetFont();
CFont newfont;
LOGFONT lf;
if(f != NULL)
{
f->GetObject(sizeof(LOGFONT), &lf);
newfont.CreateFontIndirect(&lf);
}
else
{
newfont.CreateStockObject(ANSI_VAR_FONT);
}
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;
};
myFont = GetFont();
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
{
myFont = new CFont;
myFont->Attach(f->m_hObject);
}
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;
or, alternatively
if(myFont != NULL)
{
myFont->Detach();
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)
{
}
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;
You will actually get a generic CWnd
pointer, and if you did
me->IsKindOf(RUNTIME_CLASS(CMyCoolWindowClass))
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 HANDLE
s, 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.