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.
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.
Provare a pubblicare 'WM_SYSCOMMAND' con un wParam di' SC_CLOSE'. –
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
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. –