2015-09-18 39 views
11

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.

enter image description here

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.

+0

Spróbuj opublikować 'WM_SYSCOMMAND' zamiast wParam z' SC_CLOSE'. –

+0

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

+0

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. –

Odpowiedz

0

Problem został rozwiązany, zobacz EDYCJA 3 w pytaniu dotyczącym rozwiązania.

+1

Odpowiedź nie powinna znajdować się w pytaniu. – Mangs

1

Zadzwoń pod numer ::PostQuitMessage(0);, aby zamknąć aplikację.

+0

Jeśli użyję tego, co sugerujesz, destruktory pochodnych CFormViews nigdy nie będą nazywane ... – Smash

2

Myślę, że sposób, w jaki zamykasz ramę, nie jest problemem. Domyślam się, że niszczysz jeden z widoków ręcznie, podczas gdy powinieneś pozwolić MFC je usunąć (prawdopodobnie nazywałeś DestroyWindow na jednym z nich)