【发布时间】:2015-12-16 03:12:39
【问题描述】:
根据这个stackoverflow问题:
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:
在对非自动清理对象调用 DestroyWindow 之后,C++ 对象仍然存在,但 m_hWnd 将为 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。这使析构函数挂起并且永远不会完成。
通常,第二个视图的析构函数只在第一个视图完成时调用。但在这种情况下,尽管第一个从未完成,但它仍在执行中。
由于从未调用过第一个视图基类析构函数,因此从未为第一个视图调用此函数:
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,但事实并非如此。这导致了崩溃。
【问题讨论】:
-
尝试使用
SC_CLOSE的 wParam 发布WM_SYSCOMMAND。 -
不,完全相同的问题,
pView->m_hWnd在到达ASSERT(::IsWindow(pView->m_hWnd));时已经是NULL,实际上是CFormViews,我编辑了问题以防它改变任何东西。跨度> -
此时我会放入一些跟踪语句来获取当您自己单击“X”与发送
WM_CLOSE时发生的事情的顺序。这将有助于阐明这一过程。 -
@MarkRansom 请参阅编辑 2 了解订单。
-
几年前我遇到过这样的事情。我记得我通过在有问题的视图中覆盖
PostNcDestroy,调用基本实现,然后执行delete this,选择了自动销毁机制。如果这对您有帮助,请告诉我。
标签: c++ mfc destructor