PRB: Deleting ATL Dialog Causes Assert in Atlwin.h, Line 2281
ID: Q202110
|
The information in this article applies to:
-
The Microsoft Active Template Library (ATL) 3.0, included with:
-
Microsoft Visual C++, 32-bit Editions, 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:
- DialogProc() is called with WM_CLOSE.
- ProcessWindowMessage() calls your WM_CLOSE handler.
- In your WM_CLOSE handler, you call DestroyWindow().
- This ends up calling DialogProc again with WM_NCDESTROY.
- ProcessWindowMessage() calls your WM_NCDESTROY handler.
- 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):
- DialogProc has a case for WM_NCDESTROY and calls OnFinalMessage().
- You call "delete this" in OnFinalMessage().
- 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 kbGrpMFCATL
Version : WINDOWS:3.0
Platform : WINDOWS
Issue type : kbprb