2015-09-18 19 views
11

Theo câu hỏi stackoverflow này:MFC CView (CFormView) phá hủy tai nạn

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

Tôi đang sử dụng AfxGetMainWnd()->PostMessage(WM_CLOSE,0,0); để thoát khỏi một chương trình MFC. (SDI, CFrameWnd chứa CSplitterWnd với hai CFormView)

Như dự kiến, điều này gọi DestroyWindow().

Vấn đề tôi đang phải đối mặt là sau khi sự tàn phá CFormView có nguồn gốc, theo MSDN:

Sau khi gọi DestroyWindow trên một đối tượng không tự động dọn dẹp, C++ đối tượng sẽ còn thích hợp nữa, nhưng m_hWnd sẽ NULL. [MSDN]

Bây giờ CView destructor được gọi và tại thời điểm nó làm

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

nó không thành công trên khẳng định sau đây: ASSERT(::IsWindow(pView->m_hWnd));

tôi đã kiểm tra và m_hWnd đã được thiết lập để NULL trong hàm hủy CView có nguồn gốc được gọi ngay trước đây.

Tôi đang làm gì sai?

EDIT:

Đây là biểu đồ minh họa tại sao tôi muốn gửi tin nhắn WM_CLOSE chứ không phải WM_QUIT.

enter image description here

Tôi nghĩ câu trả lời hoàn toàn sụp đổ MSDN Technical Note này, nhưng tôi không thể tìm nó ra.

EDIT 2:

Trình tự rằng mọi thứ được gọi là:

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

2- Derived CFrameWnd::OnClose()

3- CFrameWnd::OnClose()

trong đó kêu gọi CWinApp::CloseAllDocuments(BOOL bEndSession);

trong đó kêu gọi CDocManager::CloseAllDocuments(BOOL bEndSession)

trong đó kêu gọi

trong đó kêu gọi CDocument::OnCloseDocument()

Bây giờ, trong chức năng này

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 
} 

Vì vậy, chúng ta thấy rằng CWnd::DestroyWindow() được gọi, vì vậy:

4 - Derived CFormView destructor

5- CScrollView::~CScrollView()

6- CView::~CView()

trong đó kêu gọi CDocument::RemoveView(CView* pView)

trong đó kêu gọi CDocument::OnChangedViewList()

trong đó kêu gọi CDocument::UpdateFrameCounts()

nào bị treo ở đây: ASSERT(::IsWindow(pView->m_hWnd));

pView->m_hWndNULL ...

EDIT 3:

tôi đã tìm ra những gì vấn đề là:

Destructor của cái nhìn đầu tiên đã được xóa một con trỏ chưa được khởi tạo, đó là UB. Điều này đã làm cho các destructor treo và không bao giờ hoàn thành.

Thông thường, trình hủy của chế độ xem thứ hai chỉ được gọi khi hoàn thành chế độ xem đầu tiên. Nhưng trong trường hợp này nó vẫn đang được thực hiện mặc dù người đầu tiên không bao giờ hoàn thành.

Kể từ khi destructor lớp xem cơ sở đầu tiên không bao giờ được gọi là, chức năng này chưa từng được gọi cho quan điểm thứ nhất:

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 
} 

đâu chúng ta có thể thấy rằng quan điểm được lấy ra từ các m_viewList.

Điều này có nghĩa rằng khi xem destructor thứ hai hoàn thành, trong:

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

Các pos được coi là NULL, nhưng nó không phải là. Dẫn đến vụ tai nạn.

+0

Thay vào đó hãy thử đăng 'WM_SYSCOMMAND' bằng wParam của 'SC_CLOSE'. –

+0

Không, chính xác cùng một vấn đề, 'pView-> m_hWnd' đã là' NULL' khi nó đến 'ASSERT (:: IsWindow (pView-> m_hWnd)); ', nó thực sự là' CFormView's, tôi đã chỉnh sửa câu hỏi trong trường hợp nó thay đổi bất cứ điều gì. – Smash

+0

Tại thời điểm này, tôi đưa vào một số câu lệnh theo dõi để có được thứ tự xảy ra khi bạn nhấp vào "X" khi bạn gửi 'WM_CLOSE' thay thế. Điều đó sẽ làm sáng tỏ một số tiến trình. –

Trả lời

0

Sự cố đã được giải quyết, xem EDIT 3 trong câu hỏi cho giải pháp.

+1

Câu trả lời không nên có trong câu hỏi. – Mangs

1

Gọi ::PostQuitMessage(0); để đóng ứng dụng.

+0

Nếu tôi sử dụng những gì bạn đề xuất, thì các trình phá hủy của CFormView bắt nguồn không bao giờ được gọi là ... – Smash

2

Tôi nghĩ cách bạn đóng khung không phải là vấn đề ở đó. Đoán của tôi là bạn tiêu diệt một trong các khung nhìn bằng tay trong khi bạn nên để MFC xóa chúng (bạn có thể gọi là DestroyWindow trên một trong số chúng)