MFC CView(CFormView)破坏崩溃
MFC CView (CFormView) destruction crash
根据这个 Whosebug 问题:
What is the correct way to programmatically quit an MFC application?
我正在使用 AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);
退出 MFC 程序。 (SDI,CFrameWnd 包含带有两个 CFormView 的 CSplitterWnd)
正如预期的那样,这会调用 DestroyWindow()
。
我面临的问题是,在派生的 CFormView 销毁之后,根据 MSDN:
After calling DestroyWindow on a non-auto-cleanup object, the C++ object will still be around, but m_hWnd will be NULL. [MSDN]
现在 CView
析构函数被调用,此时它执行
CDocument::RemoveView()...
CDocument::UpdateFrameCounts()
它在以下断言上失败:ASSERT(::IsWindow(pView->m_hWnd));
我检查过 m_hWnd
已经在之前调用的派生 CView 析构函数中设置为 NULL。
我做错了什么?
编辑:
这是一张图表,说明了为什么我要发送 WM_CLOSE 消息而不是 WM_QUIT。
我想答案就在这个MSDN Technical Note,但我想不通。
编辑 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。这使析构函数挂起并且永远不会完成。
通常,第二个视图的析构函数仅在第一个视图完成时调用。但在这种情况下,它仍在执行,尽管第一个从未完成。
由于从未调用第一个视图基 class 析构函数,因此从未为第一个视图调用此函数:
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
,其实不是。导致崩溃。
调用 ::PostQuitMessage(0);
关闭应用程序。
我认为你关闭框架的方式不是问题所在。
我的猜测是您手动销毁了其中一个视图,而您应该让 MFC 删除它们(您可能在其中一个上调用了 DestroyWindow)
问题已解决,请参阅问题中的编辑 3 以获取解决方案。
根据这个 Whosebug 问题:
What is the correct way to programmatically quit an MFC application?
我正在使用 AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);
退出 MFC 程序。 (SDI,CFrameWnd 包含带有两个 CFormView 的 CSplitterWnd)
正如预期的那样,这会调用 DestroyWindow()
。
我面临的问题是,在派生的 CFormView 销毁之后,根据 MSDN:
After calling DestroyWindow on a non-auto-cleanup object, the C++ object will still be around, but m_hWnd will be NULL. [MSDN]
现在 CView
析构函数被调用,此时它执行
CDocument::RemoveView()...
CDocument::UpdateFrameCounts()
它在以下断言上失败:ASSERT(::IsWindow(pView->m_hWnd));
我检查过 m_hWnd
已经在之前调用的派生 CView 析构函数中设置为 NULL。
我做错了什么?
编辑:
这是一张图表,说明了为什么我要发送 WM_CLOSE 消息而不是 WM_QUIT。
我想答案就在这个MSDN Technical Note,但我想不通。
编辑 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。这使析构函数挂起并且永远不会完成。
通常,第二个视图的析构函数仅在第一个视图完成时调用。但在这种情况下,它仍在执行,尽管第一个从未完成。
由于从未调用第一个视图基 class 析构函数,因此从未为第一个视图调用此函数:
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
,其实不是。导致崩溃。
调用 ::PostQuitMessage(0);
关闭应用程序。
我认为你关闭框架的方式不是问题所在。 我的猜测是您手动销毁了其中一个视图,而您应该让 MFC 删除它们(您可能在其中一个上调用了 DestroyWindow)
问题已解决,请参阅问题中的编辑 3 以获取解决方案。