Article: Q202110
Product(s): Microsoft C Compiler
Version(s): 3.0,6.0
Operating System(s):
Keyword(s): kbATLWC kbDlg kbMMC kbVC600 kbATL300 kbFAQ kbGrpDSMFCATL
Last Modified: 14-JUN-2002
-------------------------------------------------------------------------------
The information in this article applies to:
- The Microsoft Active Template Library (ATL) 3.0, included with:
- Microsoft Visual C++, 32-bit Enterprise Edition, version 6.0
- Microsoft Visual C++, 32-bit Professional Edition, version 6.0
- Microsoft Visual C++, 32-bit Learning Edition, version 6.0
-------------------------------------------------------------------------------
SYMPTOMS
========
Deleting the C++ object associated with an ATL dialog box by calling "delete
this" in the WM_NCDESTROY handler or OnFinalMessage() results in an assert in
Atlwin.h, line 2281.
CAUSE
=====
Line 2281 of Atlwin.h is:
ATLASSERT(pThis->m_pCurrentMsg == &msg);
The object being referred to by "pThis" has already been deleted. Consider ATL's
default dialog box procedure:
template <class TBase>
LRESULT CALLBACK CDialogImplBaseT< TBase >::DialogProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CDialogImplBaseT< TBase >* pThis = (CDialogImplBaseT< TBase >*)hWnd;
// set a ptr to this message and save the old value
MSG msg = { pThis->m_hWnd, uMsg, wParam, lParam, 0, { 0, 0 } };
const MSG* pOldMsg = pThis->m_pCurrentMsg;
pThis->m_pCurrentMsg = &msg;
// pass to the message map to process
LRESULT lRes;
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
// restore saved value for the current message
ATLASSERT(pThis->m_pCurrentMsg == &msg);
pThis->m_pCurrentMsg = pOldMsg;
// set result if message was handled
if(bRet)
{
switch (uMsg)
{
case WM_COMPAREITEM:
case WM_VKEYTOITEM:
case WM_CHARTOITEM:
case WM_INITDIALOG:
case WM_QUERYDRAGICON:
case WM_CTLCOLORMSGBOX:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX:
case WM_CTLCOLORBTN:
case WM_CTLCOLORDLG:
case WM_CTLCOLORSCROLLBAR:
case WM_CTLCOLORSTATIC:
return lRes;
break;
}
::SetWindowLong(pThis->m_hWnd, DWL_MSGRESULT, lRes);
return TRUE;
}
if(uMsg == WM_NCDESTROY)
{
// clear out window handle
HWND hWnd = pThis->m_hWnd;
pThis->m_hWnd = NULL;
// clean up after dialog is destroyed
pThis->OnFinalMessage(hWnd);
}
return FALSE;
}
Typically, you will be calling DestroyWindow()/EndDialog() in a WM_CLOSE or a
WM_COMMAND handler. Here's the sequence of events when you close a dialog box:
1. DialogProc() is called with WM_CLOSE.
2. ProcessWindowMessage() calls your WM_CLOSE handler.
3. In your WM_CLOSE handler, you call DestroyWindow().
4. This ends up calling DialogProc again with WM_NCDESTROY.
5. ProcessWindowMessage() calls your WM_NCDESTROY handler.
6. You call "delete this" in your WM_NCDESTROY handler.
RESULTS: When you come back from ProcessWindowMessage(), because the C++ class
has been deleted, pThis no longer points to a valid object, and therefore the
assert is returned.
The following is another possible scenario (note that the first four steps are
the same as above):
1. DialogProc has a case for WM_NCDESTROY and calls OnFinalMessage().
2. You call "delete this" in OnFinalMessage().
3. The stack unwinds to the original DialogProc call (with a WM_CLOSE).
RESULTS: Same problem. After the call to ProcessWindowMessage, try to use pThis,
but it no longer points to valid memory.
RESOLUTION
==========
Notify DialogProc when the dialog box class has been deleted; this can be
accomplished by adding a member variable called m_bAutoDelete to the dialog box
class. Setting this to TRUE causes the dialog box class to delete itself when
the window is destroyed. Use the following code:
// Constant value used to determine if we should delete ourselves later.
#define DEFERDELETE 2
class CMyDlg : public CAxDialogImpl<CMyDlg>
{
public:
// Variable that tells us if we want to auto-delete ourselves.
BYTE m_bAutoDelete;
// Set m_bAutoDelete to TRUE to automatically delete ourselves.
CMyDlg() : m_bAutoDelete (TRUE)
{
}
// Override GetDialogProc to provide our own DialogProc.
WNDPROC GetDialogProc()
{
return MyDialogProc;
}
// Our own dialog procedure that is mostly copied from
// CDialogImplBaseT<>::DialogProc() in Atlwin.h.
static LRESULT CALLBACK MyDialogProc(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
CMyDlg* pThis = (CMyDlg*)hWnd;
// Set a ptr to this message and save the old value.
MSG msg = { pThis->m_hWnd, uMsg, wParam, lParam, 0, { 0, 0 } };
const MSG* pOldMsg = pThis->m_pCurrentMsg;
pThis->m_pCurrentMsg = &msg;
// Pass to the message map to process.
LRESULT lRes;
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam,
lParam, lRes, 0);
// If window has been destroyed and this is the last message,
// then delete ourselves.
if (DEFERDELETE == pThis->m_bAutoDelete && pOldMsg == NULL)
{
delete pThis;
return FALSE;
}
// Restore saved value for the current message.
ATLASSERT(pThis->m_pCurrentMsg == &msg);
pThis->m_pCurrentMsg = pOldMsg;
// Set result if message was handled.
if(bRet)
{
switch (uMsg)
{
case WM_COMPAREITEM:
case WM_VKEYTOITEM:
case WM_CHARTOITEM:
case WM_INITDIALOG:
case WM_QUERYDRAGICON:
case WM_CTLCOLORMSGBOX:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX:
case WM_CTLCOLORBTN:
case WM_CTLCOLORDLG:
case WM_CTLCOLORSCROLLBAR:
case WM_CTLCOLORSTATIC:
return lRes;
break;
}
::SetWindowLong(pThis->m_hWnd, DWL_MSGRESULT, lRes);
return TRUE;
}
if(uMsg == WM_NCDESTROY)
{
// Clear out window handle.
HWND hWnd = pThis->m_hWnd;
pThis->m_hWnd = NULL;
// Clean up after dialog box is destroyed.
pThis->OnFinalMessage(hWnd);
// If we want to automatically delete ourselves...
if (pThis->m_bAutoDelete)
{
// If no outstanding messages to process in call stack,
// m_pCurrentMsg will be NULL so we can delete ourselves.
if (pThis->m_pCurrentMsg == NULL)
delete pThis;
// Else set a flag so we can delete ourselves later.
else
pThis->m_bAutoDelete = DEFERDELETE;
}
}
return FALSE;
}
...
};
STATUS
======
This behavior is by design.
MORE INFORMATION
================
In CDialogImplBaseT<>::DialogProc(), m_pCurrentMsg is set so you can call
GetCurrentMessage() to retrieve the current message from any method in your
dialog class. This problem and solution applies to any CWindowImplRoot-derived
class.
REFERENCES
==========
(c) Microsoft Corporation Samson Tanrena, All Rights Reserved. Contributions by
1999, Microsoft Corporation.
Additional query words: mmc snapin snap-in OnNcDestroy
======================================================================
Keywords : kbATLWC kbDlg kbMMC kbVC600 kbATL300 kbFAQ kbGrpDSMFCATL
Technology : kbVCsearch kbAudDeveloper kbATLsearch
Version : :3.0,6.0
Issue type : kbprb
=============================================================================