Jak na tym stackoverflow pytania:MFC CView (CFormView) ruina zniszczenie
What is the correct way to programmatically quit an MFC application?
Używam AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);
aby opuścić program MFC. (SDI, CFrameWnd zawierający CSplitterWnd z dwoma CFormView)
Zgodnie z oczekiwaniami, to wywołuje DestroyWindow()
.
Problem jestem stoi to, że po pochodzącym zniszczenia CFormView, zgodnie z MSDN:
Po wywołaniu DestroyWindow na obiekcie bez automatycznego czyszczenia, obiekt C++ będzie nadal wokół, ale m_hWnd będzie być NULL. [MSDN]
Teraz CView
destructor nazywa iw punkcie to robi
CDocument::RemoveView()...
CDocument::UpdateFrameCounts()
zawiedzie na następnej assert: ASSERT(::IsWindow(pView->m_hWnd));
sprawdziłem i m_hWnd
jest już ustawiony na NULL w wyprowadzonym destruktie CView wywołanym tuż przed.
Co robię źle?
EDIT:
Oto wykres ilustrujący dlaczego chcę, aby wysłać wiadomość WM_CLOSE a nie WM_QUIT.
Myślę, że odpowiedź leży w tym MSDN Technical Note, ale nie mogę zrozumieć.
EDIT 2:
Kolejność że rzeczy się nazywa:
1- AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);
2- Derived CFrameWnd::OnClose()
3- CFrameWnd::OnClose()
który nazywa CWinApp::CloseAllDocuments(BOOL bEndSession);
który nazywa CDocManager::CloseAllDocuments(BOOL bEndSession)
który nazywa
który nazywa CDocument::OnCloseDocument()
Teraz, w tej funkcji
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
}
Widzimy więc, że CWnd::DestroyWindow()
nazywa się tak:
4 - Derived CFormView destructor
5- CScrollView::~CScrollView()
6- CView::~CView()
który nazywa CDocument::RemoveView(CView* pView)
który nazywa CDocument::OnChangedViewList()
który nazywa CDocument::UpdateFrameCounts()
Który rozbija tutaj: ASSERT(::IsWindow(pView->m_hWnd));
ponieważ pView->m_hWnd
jest NULL
...
EDIT 3:
zorientowali się, na czym polega problem:
Destruktor pierwszego widzenia było usunięcie niezainicjowanej wskaźnik, który jest UB. To powodowało, że destruktor wisiał i nigdy się nie zakończył.
Zwykle destruktor drugiego widoku jest wywoływany tylko po ukończeniu pierwszego. Ale w tym przypadku nadal był wykonywany, chociaż pierwszy nigdy nie został ukończony.
Od pierwszej klasy destruktory widok bazowe nie nazywano, funkcja ta nigdy nie został wezwany do pierwszego widzenia:
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
}
Gdzie możemy zobaczyć, że widok jest usuwany z m_viewList
.
Oznacza to, że gdy drugi widok destructor uzupełnia, w:
void CDocument::UpdateFrameCounts()
// assumes 1 doc per frame
{
// walk all frames of views (mark and sweep approach)
POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{
...
POS ma być NULL
, ale tak nie jest. Co doprowadziło do wypadku.
Spróbuj opublikować 'WM_SYSCOMMAND' zamiast wParam z' SC_CLOSE'. –
Nie, dokładnie ten sam problem, 'pView-> m_hWnd' jest już' NULL', gdy dojdzie do 'ASSERT (:: IsWindow (pView-> m_hWnd));', faktycznie jest to 'CFormView's, zredagowałem pytanie na wypadek, gdyby cokolwiek zmieniło. – Smash
W tym miejscu wstawiłbym jakieś instrukcje śledzenia, aby uzyskać kolejność, w której coś się dzieje, gdy użytkownik kliknie "X" zamiast, kiedy wyślesz 'WM_CLOSE'. To rzuci trochę światła na proces. –