MFC--Attaching and Detaching Objects

 

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 OjbectWindows Object(variant)
CWndHWNDany window
CEditHWNDEDIT
CListBoxHWNDLISTBOX
CStaticHWNDSTATIC
CComboBoxHWNDCOMBOBOX
CGDIObject(gdi object)(any)
CBitmapHBITMAP 
CBrushHBRUSH 
CPenHPEN 
CFontHFONT 
CRegionHRGN 
CPaletteHPALETTE 
CSocketSOCKET 
CAsyncSocketSOCKET 

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,

 
 

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

 
 

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

 
 
 
 

(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:

 
 
 
 
 
 
 
 
 
 

They are surprised when there is apparently no effect on the control. Which is pretty amazing, because they've done something like this before:

 
 
 
 
 
 
 
 
 
 

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

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

 
 
 
 
 
 
 
 

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):

 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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 control's WM_PAINT 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 mustn'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:

 
 
 
 
 
 
 
 
 
 
 
 

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:

 
 
 
 
 
 
 
 
 
 
 
 

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.

However, it is important to recognize that using Detach dissociates the handle with any MFC object. If you cannot retrieve the handle to finally delete the object, you will leak GDI objects. It is generally thought to be a better practice to declare a member variable of your subclass, e.g.,

CFont f; // declared in the class

and when the class is destroyed, the CFont is destroyed, and the HFONT object will also be destroyed. When I do this, I often do the creation in the constructor of the CWnd object. It is important to remember that when the constructor is executed, the window itself does not exist. So you cannot do

CMyView::CMyView()
   {
    font.CreateFont(...bunch of parameters here...); 
    SetFont(&font);                                  
   }

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:

 
 
 
 
 
 
 
 
 
 

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

 
 
 
 
 
 
 
 

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):

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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. On the other hand, 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:

 
 
 
 
 
 
 
 
 
 
 
 

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.

 
 
 
 
 
 
 
 
 
 
 
 
 
 

And in the class definition of your CWinApp class, add

 
 

and add in your CWinApp message map:

 
 

or

 
 

and the implementation method

 
 
 
 
 
 
 
 
 
 

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:

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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!1What 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:

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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), 

 
 

I can then do something like

 
 
 
 
 
 
 
 
 
 
 
 

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:

 
 
 
 
 
 
 
 
 
 
 
 

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:

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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:

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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:

 
 
 
 

or, alternatively

 
 
 
 
 
 
 
 
 
 
 
 

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:

 
 
 
 
 
 

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

 
 
 
 
 
 
 
 
 
 

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:

 
 
 
 
 
 
 
 

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

 
 

you would get FALSE. If you did

 
 
 
 

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.

Sockets

Sockets are mapped to either a CSocket or CAsyncSocket object. This mapping is in a local handle map for each thread. This means that if you pass a pointer to a C[Async]Socket object to a thread, and the thread attempts to locate that object by means of a socket object (or SOCKET object, which is a typedef for socket), it will fail, and you will get either ASSERT failures or access faults. If you want to use a socket in a separate thread, you need to pass the socket across the boundary as a raw socket object.

There are two ways to handle this. Either you can start the thread suspended and set the socket value before resuming the thread, or you can do a user-defined message and do a PostThreadMessage to send it to the thread. In the InitInstance handler or the message handler, you will do an Attach to attach the socket to a C[Async]Socket.

Method 1:

In the launching thread:

void CListenerSocket::OnAccept(UINT err)
   {
    ... deal with error code here...

    CAsyncSocket sock;

    if(Accept(thisThreadsSocket, sockaddr, sizeof(sockaddr))
       { /* got it */
        CMyThread * thread = (CMyThread *)AfxBeginThread(
                                               RUNTIME_CLASS(CMyThread),
                                               THREAD_PRIORITY_NORMAL, 0, 
                                               CREATE_SUSPENDED);

        thread->mysocket = thisThreadsSocket.Detach();
        thread->ResumeThread();
       } /* got it */
    }

in the launched thread:

class CMyThread : public CWinThread {
    ... lots of stuff
  public:
    socket mysocket;
    CAsyncSocket sock;
  ... more stuff
};

BOOL CMyThread::OnInitInstance()
    {
     sock.Attach(mysocket);
     return TRUE;
    }
Method 2:

In the launching thread:

void CListenerSocket::OnAccept(UINT err)
   {
    ... deal with error code here...

    CAsyncSocket sock;

    if(Accept(thisThreadsSocket, sockaddr, sizeof(sockaddr))
       { /* got it */
        CMyThread * thread = (CMyThread *)AfxBeginThread(
                                               RUNTIME_CLASS(CMyThread));,
        thread->mysocket = thisThreadsSocket.Detach();
        thread->PostThreadMessage(UWM_HERES_THE_SOCKET, 
                                  (WPARAM)sock.Detach(), 0);
       } /* got it */
    }

In the launched thread

class CMyThread : public CWinThread {
    ... lots of stuff
  public:
    CAsyncSocket sock;
  ... more stuff
  afx_msg LRESULT OnHeresTheSocket(WPARAM, LPARAM);
};

Add to the message map one of the two following lines:

    ON_REGISTERED_MESSAGE(UWM_HERES_THE_SOCKET, OnHeresTheSocket)
    ON_MESSAGE(UWM_HERES_THE_SOCKET, OnHeresTheSocket)

depending on which style of message you are using (see my essay on Message Management for more details)

LRESULT CMyThread::OnHeresTheSocket(WPARAM wParam, LPARAM)
   {
    socket s = (socket)wParam;
    sock.Attach(s);
    return 0;
   }

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, including sockets. 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.

 

source url:http://flounder.com/detach.htm

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/15232446/viewspace-559903/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/15232446/viewspace-559903/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值