Подтвердить что ты не робот

Крушение разрушения MFC CView (CFormView)

В соответствии с этим вопросом stackoverflow:

Каков правильный способ программного выхода из приложения MFC?

Я использую AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0); для выхода из программы MFC. (SDI, CFrameWnd, содержащий CSplitterWnd с двумя CFormViews)

Как и ожидалось, это вызывает DestroyWindow().

Проблема, с которой я сталкиваюсь, заключается в том, что после производного уничтожения CFormView, как MSDN:

После вызова DestroyWindow объекта non-auto-cleanup объект С++ будет по-прежнему находиться вокруг, но m_hWnd будет NULL. [MSDN]

Теперь вызывается деструктор CView и в точке он делает

CDocument::RemoveView()...
CDocument::UpdateFrameCounts()

он не выполняет следующие утверждения: ASSERT(::IsWindow(pView->m_hWnd));

Я проверил, и m_hWnd уже установлен в NULL в производном деструкторе CView, который вызывается как раз перед этим.

Что я делаю неправильно?

ИЗМЕНИТЬ:

Вот диаграмма, иллюстрирующая, почему я хочу отправить сообщение WM_CLOSE, а не WM_QUIT.

введите описание изображения здесь

Я думаю, что ответ лежит в этой технической заметке MSDN, но я не могу понять это.

ИЗМЕНИТЬ 2:

Порядок вызова вещей:

1- AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);

2- Derived CFrameWnd::OnClose()

3- CFrameWnd::OnClose()

который вызывает CWinApp::CloseAllDocuments(BOOL bEndSession);

который вызывает CDocManager::CloseAllDocuments(BOOL bEndSession)

который вызывает CDocTemplate::CloseAllDocuments(BOOL)

который вызывает CDocument::OnCloseDocument()

Теперь в этой функции

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
}

Итак, мы видим, что CWnd::DestroyWindow() вызывается, поэтому:

4- Derived CFormView destructor

5- CScrollView::~CScrollView()

6- CView::~CView()

который вызывает CDocument::RemoveView(CView* pView)

который вызывает CDocument::OnChangedViewList()

который вызывает CDocument::UpdateFrameCounts()

Что происходит здесь: ASSERT(::IsWindow(pView->m_hWnd));

потому что pView->m_hWnd есть NULL...

ИЗМЕНИТЬ 3:

Я понял, в чем проблема:

Деструктором первого представления было удаление неинициализированного указателя, который является UB. Это заставило деструктора повесить и никогда не заканчиваться.

Обычно деструктор второго представления вызывается только после завершения первого. Но в этом случае он все еще исполнялся, хотя первый никогда не завершался.

Поскольку первые деструкторы класса базового класса никогда не вызывались, эта функция никогда не вызывалась для первого представления:

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
}

Если мы увидим, что представление удалено из m_viewList.

Это означает, что, когда второй деструктор представления завершен, в:

void CDocument::UpdateFrameCounts()
     // assumes 1 doc per frame
{
    // walk all frames of views (mark and sweep approach)
    POSITION pos = GetFirstViewPosition();
    while (pos != NULL)
    {
...

Предполагается, что pos должен быть NULL, но это не так. Это приводит к аварии.

4b9b3361

Ответ 1

Проблема была решена, см. EDIT 3 в вопросе о решении.

Ответ 2

Я думаю, что то, как вы закрываете фрейм, не является проблемой. Я предполагаю, что вы уничтожаете одно из представлений вручную, тогда как вы должны позволить MFC удалить их (вы, вероятно, называли DestroyWindow на одном из них)

Ответ 3

Вызовите ::PostQuitMessage(0);, чтобы закрыть приложение.