2015-09-18 25 views
11

Secondo questa domanda StackOverflow:MFC CView (CFormView) distruzione incidente

What is the correct way to programmatically quit an MFC application?

Sto usando AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0); per uscire da un programma MFC. (SDI, CFrameWnd contenente un CSplitterWnd con due CFormViews)

Come previsto, chiama DestroyWindow().

Il problema che sto affrontando è che dopo la distruzione CFormView derivato, come da MSDN:

Dopo aver chiamato DestroyWindow su un oggetto non-auto-pulizia, il C++ oggetto sarà ancora in giro, ma m_hWnd volontà essere NULL. [MSDN]

Ora il distruttore CView si chiama e dal punto in cui fa il

CDocument::RemoveView()... 
CDocument::UpdateFrameCounts() 

fallisce sulla seguente asserzione: ASSERT(::IsWindow(pView->m_hWnd));

ho controllato e il m_hWnd è già impostata su NULL nel distruttore CView derivato chiamato poco prima.

Cosa sto sbagliando?

EDIT:

Ecco un grafico che illustra il motivo per cui voglio inviare un messaggio WM_CLOSE e non un WM_QUIT.

enter image description here

Penso che la risposta depone in questo MSDN Technical Note, ma non riesco a capirlo.

EDIT 2:

L'ordine in cui le cose vengono chiamati:

1- AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0);

2- Derived CFrameWnd::OnClose()

3- CFrameWnd::OnClose()

che chiama CWinApp::CloseAllDocuments(BOOL bEndSession);

che chiama CDocManager::CloseAllDocuments(BOOL bEndSession)

che chiama

che chiama CDocument::OnCloseDocument()

Ora, in questa funzione

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 
} 

Così vediamo che CWnd::DestroyWindow() si chiama, in modo da:

4 - Derived CFormView destructor

5- CScrollView::~CScrollView()

6- CView::~CView()

che chiama CDocument::RemoveView(CView* pView)

che chiama CDocument::OnChangedViewList()

che chiama CDocument::UpdateFrameCounts()

Che si blocca qui: ASSERT(::IsWindow(pView->m_hWnd));

perché pView->m_hWnd è NULL ...

EDIT 3:

ho capito quale fosse il problema:

Il distruttore del primo punto di vista è stata l'eliminazione di un puntatore non inizializzato, che è UB. Ciò stava bloccando il distruttore e non si è mai completato.

In genere, il distruttore della seconda vista viene richiamato solo al completamento del primo. Ma in questo caso era ancora in esecuzione, anche se il primo non è mai stato completato.

Poiché i primi distruttori classe vista di base non sono mai stati chiamati, questa funzione non è mai stato chiamato per la prima vista:

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 
} 

Dove possiamo vedere che la vista viene rimosso dal m_viewList.

Questo significa che quando la seconda vista distruttore completa, in:

void CDocument::UpdateFrameCounts() 
    // assumes 1 doc per frame 
{ 
    // walk all frames of views (mark and sweep approach) 
    POSITION pos = GetFirstViewPosition(); 
    while (pos != NULL) 
    { 
... 

Si suppone che la pos essere NULL, ma non è. Che portano allo schianto.

+0

Provare a pubblicare 'WM_SYSCOMMAND' con un wParam di' SC_CLOSE'. –

+0

No, esattamente lo stesso problema, 'pView-> m_hWnd' è già' NULL' quando arriva a 'ASSERT (:: IsWindow (pView-> m_hWnd)),', in realtà 'CFormView's, ho modificato la domanda nel caso cambi qualcosa. – Smash

+0

A questo punto inserisco alcune istruzioni di traccia per ottenere l'ordine in cui si verificano le cose quando si fa clic sulla "X" contro quando si invia 'WM_CLOSE'. Ciò farà luce sul processo. –

risposta

0

Il problema è stato risolto, vedi EDIT 3 nella domanda per la soluzione.

+1

La risposta non dovrebbe essere nella domanda. – Mangs

1

Chiama ::PostQuitMessage(0); per chiudere l'app.

+0

Se utilizzo ciò che suggerisci, i distruttori delle CFormViews derivate non vengono mai chiamati ... – Smash

2

Penso che il modo in cui si sta chiudendo la cornice non sia il problema. La mia ipotesi è di distruggere una delle visualizzazioni a mano mentre dovresti lasciare che MFC le cancelli (probabilmente hai chiamato DestroyWindow su una di esse)