C++ Q & A

Paul DiLascia

Paul DiLascia is a freelance software consultant specializing in training and software development in C++ and Windows. He is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992).

Q. In the June 1996 issue, you mentioned the temporary/permanent handle map mechanism in MFC. When I tried to browse though the source code about it, I just got confused. Could you throw some light on this confusing topic?

Joe Chau

Altera Corporation

A. When you program Windows® with MFC, it’s important to have a clear understanding of the difference between the window that lives on the screen and the C++ CWnd object that represents it. This topic has come up in my column from time to time over the years, but it’s the kind of thing you can never understand too well, so why not review it again now?

Centuries ago, when people programmed Windows using mere C, there were just windows, which were manipulated using HWNDs. An HWND is, as the name suggests, a handle to a WND. WND is a private, Windows-internal structure that holds information the operating system needs about a window, like its position, title, and so on. You can’t access WND yourself (unless you’re using techniques from one of those “undocumented secrets” books that Andrew Schulman and Matt Pietrek write). Instead, you call API functions like GetWindowRect and GetWindowText, passing the HWND as an argument. From your perspective, the HWND is just a magic cookie that identifies the window; the fact that it is actually a memory handle to a WND struct is irrelevant.

Windowsæthat is, HWNDsæbehave like objects in the sense that they come in different classesæsuch as Button, List Box, and so onæand you can send them messages via ::SendMessage to make them do things like paint themselves or move. So even before C++ was invented, the Windows API was loosely object-oriented.

When C++ came along, the question became: how do you program this stuff in C++? The first thing you need is a C++ class to represent a window. In MFC, that class is CWnd. (In MFC, instead of dealing with HWNDs, you deal with CWnds.) Inside every CWnd is a member, m_hWnd, that holds the HWND. MFC uses m_hWnd to translate MFC calls into C API calls. Most Win32 API functions have MFC counterparts written as thin wrappersæsmall inline functions that merely pass the buck to Win32. For example:

inline void CWnd::SetWindowText(LPCTSTR lpsz)
{ 
    ::SetWindowText(m_hWnd, lpszString); 
}

So when you write

pWnd->SetWindowText("Mumble");

the compiler generates exactly the same code as if you’d written this:

::SetWindowText(pWnd->m_hWnd, "Mumble"); 

MFC initializes CWnd::m_hWnd when you create the window. In the plain vanilla scenario, you have some window like CMyWnd derived from CWnd and you call CWnd::Create or CreateEx to create it. MFC calls ::Create­Window or ::CreateWindowEx to create the window and stores the HWND returned in m_hWnd. Now the CWnd points to its window. But how does MFC go the other way? That is, given an HWND, how does MFC find the corresponding CWnd and why is that important?

There are many times MFC needs to get the CWnd for a given HWND. In particular, MFC has buried inside it a universal window procedure, AfxWndProc, that handles window messages by routing them to the appropriate CWnd objects and message map function. Remember, underneath all the object-oriented fluff, everything reduces to ordinary Windows C API programming. Every window still has a window procedure that handles messages like WM_
CREATE and WM_PAINT. But the window procedure that MFC usesæAfxWndProcætranslates these messages into calls to the appropriate CWnd-derived class member functions like CYourWnd::OnCreate and CYourWnd::OnPaint. But when the message arrives at AfxWndProc, MFC doesn’t get a CWnd object; all it gets is the standard arguments: HWND, message code, wParam, and lParam. So how does MFC find the CWnd?

There are many ways of doing it. One way would be to store the CWnd pointer as a window property. Windows lets you store any 32-bit values you want as a property using functions ::GetProp and ::SetProp. For example, the CWnd::­Create function, after creating the window, could do something like this:

// hypothetical
CWnd::Create()
{

·

·

·

    m_hWnd = ::CreateWindowEx(...);
    ::SetProp(m_hWnd, "CWndProp", (HANDLE)this);

·

·

·

}

Now, to get the CWnd, MFC’s window procedure could do something like this:

// hypothetical
AfxWndProc(HWND hWnd, UINT, uMsg, WPARAM wp, LPARAM lp)
{
    CWnd* pWnd = 
        (CWnd*)::GetProp(hWnd, "CWndProp");
    // Dispatch message through pWnd

·

·

·

}

In this hypothetical example, CWndProp is an arbitrary string that names the property. It can be anything as long as the code setting and getting the property uses the same string. The property mechanism works fine. In fact, I once wrote a C++ frameworkæin my book Windows++ (Addison-Wesley, 1992)æthat uses properties to hold the CWnd pointer. But the folks who built MFC felt the property mechanism might be too slow, particularly if the program­mer’s application also uses lots of properties for something else. (To be honest, I don’t think I’ve ever seen a program that uses window properties.) Keep in mind that AfxWndProc is called every time a message is processed, which is practically all the time. So it’s extremely important that whatever mechanism the framework uses to get the CWnd be fast.

The fastest and simplest alternative to using properties would be to ask the operating system folks to add another 32 bits to the WND structure (the internal OS structure that HWND is a handle to), which MFC could then use to store its CWnd pointer. I’m sure the developers in MFC-land would love to get their own field in the WND structure. Personally, I don’t know why they haven’t done this. Perhaps because multiple processes might communicate with the same window through multiple CWnd objects, so a single 32-bit field isn’t enough; there would have to be one per thread.

In any case, MFC does not have its own field in the WND structure, so something more inventive is required to map HWNDs to CWnds. This is where the permanent and temporary maps come in. MFC maintains a global map that’s essentially just a dictionary or association table that associates HWNDs and CWnds. Conceptually, it works like this. CWnd::Create sets up the association:

// pseudo-code
CWnd::Create()
{

·

·

·

    m_hWnd = ::CreateWindowEx(...);
    globalMap.Add(m_hWnd, this);

·

·

·

}

Once the association is added to the global map, all Afx­WndProc has to do is look it up:

// pseudo-code
AfxWndProc(HWND hWnd, UINT, uMsg, WPARAM wp, LPARAM lp)
{
    CWnd* pWnd = globalMap.Lookup(hWnd);
    // Dispatch message using CWnd message map

·

·

·

}

Of course, the actual implementation is trickier than I’ve made it look. In real life, MFC doesn’t add the association after ::CreateWindowEx returns; that would be too late because ::CreateWindowEx sends a few messages like WM_
NCCREATE, WM_CREATE, and WM_GETMIN­MAX­INFO, which would be lost if the association was added after CreateWindowEx returns. So instead, MFC sets up a windows hook to trap WM_NCCREATE, then sets up the link in the hook procedure. That way, CWnd objects can handle the early creation messages like WM_NC­CREATE and WM_CREATE as they would any other message.

For the purpose of understanding what’s going on, the details of how globalMap is implemented are unimportant. The important thing to know is that there is such a map, and MFC uses it to find the CWnd associated with an HWND.

What I have called globalMap in the pseudo-code above corresponds to what MFC calls the permanent map. In fact, there are two maps, permanent and temporary. All of what I’ve just described applies to windows that your application explicitly createsæyour main frame window (CMainFrame), your views and dialogs, and so on. With all these windows, you call some MFC create function to explicitly create a window. Usually it’s CWnd::Create or CWnd::CreateEx, but it could be something else like CDialog::DoModal in the case of a modal dialog, SubclassDlgItem in the case of a dialog control, or some other overloaded Create function. MFC adds the HWND/CWnd association to its permanent window map, and everything flies.

This works fine and dandy for windows you have explicitly created, but sometimes you need a CWnd corresponding to a window you didn’t create. For example, suppose your app wants to see whether there’s a desktop window called Fooble Window, and if so, send it a message.

CWnd *pWnd = 
    CWnd::FindWindow(NULL,"Fooble Window");
if (pWnd)
    pWnd->SendMessage(WM_MYMESSAGE);

If there’s a top-level window with the title Fooble Window, MFC returns a CWnd that represents this window. But where does the CWnd come from? Your app hasn’t created it! Fooble Window might be another application entirely, one you didn’t even write, one that isn’t even written in MFC! So where does the CWnd come from?

This is where temporary maps come in. MFC’s implementation of CWnd::FindWindow looks something like the following:

CWnd* CWnd::FindWindow(LPCTSTR lpszClassName, 
                       LPCTSTR lpszWindowName)
{ 
    return CWnd::FromHandle(::FindWindow(lpszClassName,
                                         lpszWindowName)); 
}

CWnd::FindWindow calls the global ::FindWindow, then passes the HWND to CWnd::FromHandle to create a CWnd for it. CWnd::FromHandle is the interesting function here. Once again, I’m paraphrasing the code, but essentially it looks something like this:

// pseudo-code
CWnd* CWnd::FromHandle(HWND hWnd)
{
    CWnd* pWnd = globalMap.Lookup(hWnd);
    if (!pWnd) {
        // not in permanent map
        pWnd = tempMap.Lookup(hWnd);
        if (!pWnd) {
            pWnd = new CWnd(hWnd);
            tempMap.Add(hWnd, pWnd);
        }
    }
    return pWnd;
}

If the HWND doesn’t exist in the permanent map (global­Map), MFC looks in the temporary map. If the HWND isn’t there, MFC creates a CWnd on-the-fly, hooks it up to the HWND, adds it to the temporary map, and returns it. CWnd::FromHandle is guaranteed to return a CWnd whether your program created it or not.

Astute readers will be wondering: why is this other map I’ve described called the temporary map? Answer: because the objects in it exist only temporarily. Duh. One of the things MFC does as part of its normal idle processing (in CWinThread::OnIdle) is delete all the temporary objects.

// pseudo-code
BOOL CWinThread::OnIdle(LONG lCount)
{

·

·

·

    if (lCount >= 0)
        tempMap.DeleteAll();
}

Why does MFC do this? For one thing, to reclaim memory. But also because your program might later decide to create a permanent window for one that was once temporary, which won’t work because MFC can have only one CWnd object for each HWND. You might think: fine, just use the temporary one. But that won’t work because temporary windows are always CWnds, no matter what the actual window is. For example, if you have a dialog with an edit control and you call CWnd::GetDlgItem to get a pointer to the edit control, then—assuming you haven’t subclassed the edit control with your own CEdit object—the MFC object returned is a CWnd, not a CEdit!

This is a common source of confusion to beginning MFC programmers. I’ve often seen code like this:

CMyDialog::SomeFn(...)
{
    CEdit* pEdit = 
        (CEdit*)GetDlgItem(ID_EDIT1); // NOT!!
    pEdit->GetSel(...);

·

·

·

}

Technically, this code is incorrect because—once again, assuming you haven’t created a CEdit for the ID_EDIT1 control—GetDlgItem returns a CWnd, not a CEdit. You can cast to CEdit all you like, but the object is still a CWnd. In practice, however, this code works because all CEdit::GetSel does is send EM_GETSEL to the window, which, being a true edit control, it responds to. But if GetSel was a virtual function instead of a wrapper, or if it used some data member that was specific to CEdit, this code would crash.

In short, MFC creates temporary window objects only so you can then pass them immediately to other MFC functions that require CWnds. What does this mean to you? As a practical matter, it means that you should never store a pointer to a temporary window.

// NOT!!
m_pMyWnd = CWnd::FindWindow("Fooble Window");

If the object returned from FindWindow is a temporary window, MFC will delete it on the next idle cycle, leaving your m_pMyWnd pointing to outer space. The only thing you can do with a temporary window object is use it right away; that is, pass it to some other function that requires a CWnd. If you want a permanent CWnd for Fooble Window, you can create your own with new and Attach it.

Some of you might be wondering: how do I know if a CWnd returned by an MFC function is temporary or not? Answer: read the documentation. The reference manual will tell you when it’s not safe to hold on to a CWnd pointer. Generally speaking, however, it’s obvious. For example, you may be iterating over all the child windows of a window. Unless you’ve created your own window objects for these windows they don’t exist, and CWnd::GetWindow and CWnd::GetNextWindow may return temporary objects. If for some reason you need to get a window object only if it’s permanent, you can call CWnd::FromHandlePermanent. As you would expect from the name, this function returns a pointer to the CWnd object corresponding to an HWND, but only if that object is in the permanent map. Figure 1 shows some other functions that you may find useful for dealing with temporary/permanent handle maps.

Figure 1 Handle Map Functions

static CWnd* CWnd::FromHandle(HWND hWnd) 

Returns a pointer to the CWnd for given HWND. Will create a temporary object on-the-fly if necessary. Guaranteed to return a non-NULL CWnd (assuming the HWND is valid).

static CWnd* CWnd::FromHandlePermanent(HWND hWnd)

Returns a pointer to the CWnd for given HWND, only if the CWnd already exists in the permanent map. Otherwise, returns NULL.

void PASCAL CWnd::SendMessageToDescendants(HWND hWnd,  
        UINT message, WPARAM wParam, LPARAM lParam,
        BOOL bDeep, BOOL bOnlyPerm)

This useful function sends a message to a window and all its descendants. The last argument controls whether the message is sent to permanent objects only, or to all windows.

void AfxLockTempMaps

Locks temporary maps in memory. Advanced; see the MFC source file winhand.cpp for more info.

BOOL AfxUnlockTempMaps(BOOL bDelete)

Unlocks temporary maps. Advanced; see the MFC source file winhand.cpp for more info.

Notice I said “handle maps,” not “window handle maps.” In fact, MFC uses the same handle map mechanism for CDC, CGdiObject (which is the base class for many objects like CPen, CBrush, CFont, and so on), CMenu, and CImageList, as well as CWnd. All these classes have FromHandle functions that create a temporary object on- the-fly if a permanent one doesn’t exist. And, as with CWnd, the temporary maps for all these objects are flushed on every idle cycle.

I haven’t described the details of how the maps are implemented because there’s no reason you should ever have to know. In fact, the maps are implemented using CMapPtrToPtr, one of the standard MFC collection classes. Because CMapPtrToPtr uses a hash table, lookup is quick. If you really want to know how MFC implements handle maps, look in the files winhand_.h and winhand.cpp in
the MFC source code directory (\MSVC\VC\MFC\SRC). Figure 2 shows the definition of CHandleMap, the class MFC uses to hold a map for one kind of object like CWnd or CMenu. The module thread state holds a pointer to each of the maps. For example, to get the window map, you can write:

AFX_MODULE_THREAD_STATE* pState = 
    AfxGetModuleThreadState();
CHandleMap* pMap = pState->m_pmapHWND;

Figure 2 CHandleMap

////////////////
// This is the class MFC uses to implement a handle map.
// It holds both permanent and temporary objects. The module thread
// state (see below) holds a pointer to each map for windows, menus,
// device contexts, GDI objects, and image lists. In other words, the
// map is a thread-global. Actual definition is in mfc\src\winhand_.h
// 
class CHandleMap {
private:
   CMapPtrToPtr m_permanentMap;  // permanent 
   CMapPtrToPtr m_temporaryMap;  // temporary
   CRuntimeClass*  m_pClass;     // e.g. CWnd
   size_t m_nOffset;             // offset of handles in obj
   int m_nHandles;               // 1 or 2 (for CDC)

public:
   CHandleMap(CRuntimeClass* pClass, size_t nOffset, int nHandles = 1);
   ~CHandleMap()
      { DeleteTemp(); }

   CObject* FromHandle(HANDLE h);
   void DeleteTemp();
   void SetPermanent(HANDLE h, CObject* permOb);
   void RemoveHandle(HANDLE h);
   CObject* LookupPermanent(HANDLE h);
   CObject* LookupTemporary(HANDLE h);
   friend class CWinThread;
};


////////////////
// This is the module thread state, which holds all sorts
// of information about a thread. I've only shown the maps here.
// Actual definition is in mfc\include\afxstat__.h
//
class AFX_MODULE_THREAD_STATE : public CNoTrackObject {
public:

·

·

·

   // temporary/permanent map state
   DWORD m_nTempMapLock;           // if not 0, temp maps locked
   CHandleMap* m_pmapHWND;
   CHandleMap* m_pmapHMENU;
   CHandleMap* m_pmapHDC;
   CHandleMap* m_pmapHGDIOBJ;
   CHandleMap* m_pmapHIMAGELIST;

·

·

·

};

Notice that each handle map is a thread global. This explains a whole category of problems that arise when you try to write multithreaded apps: you can’t pass a CWnd object from one thread to another, and MFC advises against it. Sometimes it will work, but in general it fails because, among other things, the AssertValid function for CWnds checks that the CWnd object exists in the (temporary or permanent) handle map associated with the same HWND as m_hWnd. But the CWnd only exists in the thread where it was created, or the thread from which CWnd::FromHandle was called in the case of a temporary object. That CWnd will not exist in the handle map of any other thread, and if you pass it to another thread, the code is sure to crash as soon as it runs into one of the zillions of ASSERT_VALID checks sprinkled throughout MFC.

I wrote a program called ViewMaps (see Figures 3 and 4) that dumps the contents of the permanent and temporary window maps to the diagnostic (TRACE) stream. There are two commands, Dump Window Maps and Dump with Top­level Windows. The first command dumps the contents of the temporary and permanent window maps. Figure 5 shows the results in my TRACEWIN utility (you can also see them by running under the debugger in Visual C++® 5.0). As expected, the temporary map is empty. That’s because whenever an app is sitting idle, there should be no temporary objectsæany that might have existed are immediately destroyed on the first idle cycle.

Figure 3 ViewMaps.cpp

////////////////////////////////////////////////////////////////
// VIEWMAPS 1997 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// VIEWMAPS displays the permanent and temporary window maps as TRACE output.
// Compiles with VC++ 5.0 or later under Windows 95.

#include "StdAfx.h"
#include "resource.h"       // main symbols

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////
// Vanilla MFC app
//
class CMyApp : public CWinApp {
public:
   DECLARE_DYNAMIC(CMyApp)
   CMyApp();
   virtual BOOL InitInstance();
protected:
   afx_msg void OnAppAbout();
   afx_msg void OnDump();
   afx_msg void OnDumpAll();
   DECLARE_MESSAGE_MAP()
};

////////////////
// Standard MFC main frame window
//
class CMainFrame : public CFrameWnd {
public:
   DECLARE_DYNCREATE(CMainFrame)
   CMainFrame();
   virtual ~CMainFrame();
protected:
   CStatusBar  m_wndStatusBar;
   CToolBar    m_wndToolBar;
   afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
   DECLARE_MESSAGE_MAP()
};

////////////////////////////////////////////////////////////////
// CMyApp
//
BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
   ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
   ON_COMMAND(ID_DUMP,      OnDump)
   ON_COMMAND(ID_DUMP_ALL,  OnDumpAll)
END_MESSAGE_MAP()

IMPLEMENT_DYNAMIC(CMyApp, CWinApp)

CMyApp::CMyApp()
{
}

CMyApp theApp;

BOOL CMyApp::InitInstance()
{
#ifdef NEVER
   CSingleDocTemplate* pDocTemplate;
   pDocTemplate = new CSingleDocTemplate(
      IDR_MAINFRAME,
      RUNTIME_CLASS(CMyDoc),
      RUNTIME_CLASS(CMainFrame),       // main SDI frame window
      RUNTIME_CLASS(CMyView));
   AddDocTemplate(pDocTemplate);
   CCommandLineInfo cmdInfo;
   ParseCommandLine(cmdInfo);
   return ProcessShellCommand(cmdInfo);
#endif 

   CMainFrame* pMainFrame = new CMainFrame;
   if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
      return FALSE;
   pMainFrame->ShowWindow(m_nCmdShow);
   pMainFrame->UpdateWindow();
   m_pMainWnd = pMainFrame;

   return TRUE;
}

void CMyApp::OnAppAbout()
{
   CDialog(IDD_ABOUTBOX).DoModal();
}

//////////////////
// This is the handle map that MFC uses for all Windows objects that have MFC
// counterparts, like windows, menus, and GDI drawing objects (pen, font, etc.)
// This definition is copied from the MFC source file winhand_.h, and must
// exactly match that definition.
//
class CMyHandleMap {
private:                   // implementation
   CMyHandleMap();         // constructor private: never constructed
public:
   CMapPtrToPtr m_permanentMap;
   CMapPtrToPtr m_temporaryMap;
   CRuntimeClass*  m_pClass;
   // Following are not used by me--MFC uses for HDC's
   size_t m_nOffset;       // offset of handles in the object
   int m_nHandles;         // 1 or 2 (for CDC)
};

//////////////////
// Dump the contents of a map (permanent or temporary)
//
static void DumpMap(CMapPtrToPtr& map, LPCTSTR lpszTitle)
{
   TRACE(lpszTitle);
   void *key, *val;

   // Loop for each window in the map
   //
   POSITION pos = map.GetStartPosition();
   if (pos==NULL) {
      TRACE("   (empty)\n");
   } else {
      while (pos!=NULL) {
         map.GetNextAssoc(pos, key, val);
         CWnd* pWnd = (CWnd*)val;
         HWND hwnd = pWnd->GetSafeHwnd();
         ASSERT_VALID(pWnd);

         // Get window title
         //
         CString sTitle;
         pWnd->GetWindowText(sTitle);

         // Get window class name
         //
         char classname[256]="";
         if (hwnd)
            GetClassName(hwnd, classname, sizeof(classname));

         // Display window's runtime class, HWND, class name, and title
         //
         TRACE("   %s[0x%04x,\"%s\",\"%s\"]\n", 
               pWnd->GetRuntimeClass()->m_lpszClassName,
               hwnd, classname, (LPCTSTR)sTitle);
      }
   }
}

//////////////////
// Handle Dump command:
// Show contents of permanent and temporary maps.
//
void CMyApp::OnDump()
{
   AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
   CMyHandleMap* pMap = (CMyHandleMap*)pState->m_pmapHWND;
   ASSERT(pMap);
   DumpMap(pMap->m_permanentMap, "Permanent map:\n");
   DumpMap(pMap->m_temporaryMap, "Temporary map:\n");
}

//////////////////
// Handle Dump All command:
// Like Dump, but first get CWnd's for all toplevel windows.
//
void CMyApp::OnDumpAll()
{
   for (CWnd* pw = CWnd::GetDesktopWindow()->GetTopWindow();
        pw;
        pw=pw->GetNextWindow()) {

      // Do nothing: The CWnds will stay in the temporary map until
      // the next idle cycle cleans them up—which happens after my function
      // returns at the earliest.
   }
   OnDump();
}

////////////////////////////////////////////////////////////////
// CMainFrame
//
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
   ON_WM_CREATE()
END_MESSAGE_MAP()

IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)

static UINT indicators[] = {
   ID_SEPARATOR,           // status line indicator
   ID_INDICATOR_CAPS,
   ID_INDICATOR_NUM,
   ID_INDICATOR_SCRL,
};

CMainFrame::CMainFrame()
{
}

CMainFrame::~CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
      return -1;
   
   if (!m_wndToolBar.Create(this) ||
      !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) {
      TRACE0("Failed to create toolbar\n");
      return -1;      // fail to create
   }

   if (!m_wndStatusBar.Create(this) ||
      !m_wndStatusBar.SetIndicators(indicators,
        sizeof(indicators)/sizeof(UINT))) {
      TRACE0("Failed to create status bar\n");
      return -1;      // fail to create
   }

   // TODO: Remove this if you don't want tool tips or a resizeable toolbar
   m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
      CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);

   return 0;
}

Figure 4 ViewMaps

Figure 5 Dump Window Maps

The second command works just like the first, except that before dumping the contents of the maps, ViewMaps calls CWnd::GetTopWindow/GetNextWindow to get the CWnd for each top-level child window on the desktop. My program doesn’t do anything with these CWnds; all it does is call CWnd::GetNextWindow. But this causes MFC to create a temporary CWnd for each of these windows, and these appear in the dump, as you can see in Figure 6. If you then do an ordinary dump, the temporary objects are gone, proving that temporary objects are, in fact, fleeting. u

Figure 6 Dump with Toplevel Windows

To obtain complete source code listings, see page 5.

Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com