Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MFC CView (CFormView) destruction crash

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.

like image 746
Smash Avatar asked Sep 18 '15 21:09

Smash


Video Answer


1 Answers

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)

like image 151
BadJerry Avatar answered Oct 21 '22 22:10

BadJerry