As per this stackoverflow question:
What is the correct way to programmatically quit an MFC application?
I am using AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);
to exit an MFC program. (SDI, CFrameWnd containing a CSplitterWnd with two CFormViews)
As expected, this calls DestroyWindow()
.
The problem I am facing is that after the derived CFormView destruction, as per MSDN:
After calling DestroyWindow on a non-auto-cleanup object, the C++ object will still be around, but m_hWnd will be NULL. [MSDN]
Now the CView
destructor is called and at the point it does the
CDocument::RemoveView()...
CDocument::UpdateFrameCounts()
it fails on the following assert: ASSERT(::IsWindow(pView->m_hWnd));
I checked and the m_hWnd
is already set to NULL in the derived CView destructor called just before.
What am I doing wrong ?
EDIT:
Here is a chart illustrating why I want to send a WM_CLOSE message and not a WM_QUIT.
I think the answer lays in this MSDN Technical Note, but I can't figure it out.
EDIT 2:
The order that things get called:
1- AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);
2- Derived CFrameWnd::OnClose()
3- CFrameWnd::OnClose()
which calls CWinApp::CloseAllDocuments(BOOL bEndSession);
which calls CDocManager::CloseAllDocuments(BOOL bEndSession)
which calls CDocTemplate::CloseAllDocuments(BOOL)
which calls CDocument::OnCloseDocument()
Now, in this function
while (!m_viewList.IsEmpty())
{
// get frame attached to the view
CView* pView = (CView*)m_viewList.GetHead();
ASSERT_VALID(pView);
CFrameWnd* pFrame = pView->EnsureParentFrame();
// and close it
PreCloseFrame(pFrame);
pFrame->DestroyWindow();
// will destroy the view as well
}
So we see that CWnd::DestroyWindow()
is called, so:
4- Derived CFormView destructor
5- CScrollView::~CScrollView()
6- CView::~CView()
which calls CDocument::RemoveView(CView* pView)
which calls CDocument::OnChangedViewList()
which calls CDocument::UpdateFrameCounts()
Which crashes here: ASSERT(::IsWindow(pView->m_hWnd));
because pView->m_hWnd
is NULL
...
EDIT 3:
I figured out what the problem was:
The destructor of the first view was deleting an uninitialized pointer, which is UB. This was making the destructor hang and never complete.
Usually, the destructor of the second view is only called upon completion of the first one. But in this case it was still being executed although the first one never completed.
Since the first view base class destructors were never called, this function was never called for the first view:
void CDocument::RemoveView(CView* pView)
{
ASSERT_VALID(pView);
ASSERT(pView->m_pDocument == this); // must be attached to us
m_viewList.RemoveAt(m_viewList.Find(pView));
pView->m_pDocument = NULL;
OnChangedViewList(); // must be the last thing done to the document
}
Where we can see that the view is removed from the m_viewList
.
This means that when the second view destructor completes, in:
void CDocument::UpdateFrameCounts()
// assumes 1 doc per frame
{
// walk all frames of views (mark and sweep approach)
POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{
...
The pos is supposed to be NULL
, but it is not. Which lead to the crash.
WM_SYSCOMMAND
with a wParam ofSC_CLOSE
instead. – Mark RansompView->m_hWnd
is alreadyNULL
when it gets toASSERT(::IsWindow(pView->m_hWnd));
, it is actuallyCFormView
s, I edited the question in case it changes anything. – SmashWM_CLOSE
instead. That will shed some light on the process. – Mark RansomPostNcDestroy
in the offending view, calling the base implementation and then doingdelete this
. Let me know if this helps you. – Andy Brown