11
votes

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.

enter image description here

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.

3
Try posting WM_SYSCOMMAND with a wParam of SC_CLOSE instead.Mark Ransom
Nope, exact same issue, pView->m_hWnd is already NULL when it gets to ASSERT(::IsWindow(pView->m_hWnd));, it is actually CFormViews, I edited the question in case it changes anything.Smash
At this point I'd put in some trace statements to get the order that things occur when you click the "X" yourself vs. when you send WM_CLOSE instead. That will shed some light on the process.Mark Ransom
@MarkRansom see EDIT 2 for the order.Smash
I bumped into something like this some years back. As I recall it I opted into the auto-destruct mechanism by overriding PostNcDestroy in the offending view, calling the base implementation and then doing delete this. Let me know if this helps you.Andy Brown

3 Answers

2
votes

I think the way you are closing the frame is not the issue there. My guess is that you destroy one of the views by hand whereas you should let MFC delete them (you probably called DestroyWindow on one of them)

1
votes

Call ::PostQuitMessage(0); to close the app.

0
votes

The problem was resolved, see EDIT 3 in the question for the solution.