В соответствии с этим вопросом 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
, но это не так. Это приводит к аварии.