MFC 視窗銷燬過程
考慮單視窗情況:
假設自己通過new建立了一個視窗物件pWnd,然後pWnd->Create。則銷燬視窗的呼叫次序:
1. 手工呼叫pWnd->DestroyWindow();
2. DestroyWindow會發送WM_DESTROY;
3. WM_DESTROY對應的訊息處理函式是OnDestroy();
4. DestroyWindow會發送WM_NCDESTROY;
5. WM_NCDESTROY對應的訊息處理函式是OnNcDestroy;
6. OnNcDestroy最後會呼叫PostNcDestroy;
7. PostNcDestroy經常被使用者過載以提供釋放記憶體操作。例如可以使用delete this;
通過這種方式,視窗物件對應的視窗和視窗物件本身都被釋放了。
如果含有子視窗:
如果含有子視窗,則呼叫父視窗的DestroyWindow時,它會向子視窗傳送WM_DESTROY和WM_NCDESTROY訊息。
具體呼叫順序參考下文的例子。
DestroyWindow
應該說前者對後者並沒有什麼影響。但經常在DestroyWindow間接導致執行的PostNcDestroy中delete視窗物件指標,即 delete this。
CView::PostNcDestroy中唯一的操作就是delete this;CframeWnd::PostNcDestory也是如此。而預設的CWnd::PostNcDestroy是空操作,CDialog中也沒 有對其進行過載,即也是空。
delete對Destroy的影響:
delete會導致解構函式。CWnd的解構函式中有對DestroyWindow的呼叫,但必須保證:
m_hWnd != NULL &&
this != (CWnd*) &wndTop &&this != (CWnd*)&wndBottom &&
this != (CWnd*)&wndTopMost &&this != (CWnd*)&wndNoTopMost。
Cdialog的解構函式中也有對DestroyWindow的呼叫,但條件比較鬆,只需要m_hWnd != NULL。另外Cdialog::DoModal也會呼叫DestroyWindow。
CFrameWnd的OnClose中會呼叫DestroyWindow,但其析構中不會呼叫DestroyWindow。
CView的析構也不會呼叫DestroyWindow。
一個
有CMainFrame類、CMyView類。並且CMyView有兩個子視窗CMyDlg和CmyWnd的例項。
點選退出按鈕,CMainFrame會收到WM_CLOSE訊息。CframeWnd(CMainFrame的父類)間接會呼叫 CWnd::DestroyWindow;它首先向CMyView傳送WM_DESTORY和WM_NCDESTROY訊息,並引發相應的處理函式;然後 向CMyDlg傳送WM_DESTORY和WM_NCDESTROY訊息,並引發相應的處理函式;然後向CMyWnd傳送WM_DESTORY和 WM_NCDESTROY訊息,並引發相應的處理函式。
具體的執行順序是:
1. 呼叫CMainFrame::DestroyWindow
2. CFrameWnd::OnDestroy
3. CMyView::OnDestroy
4. CmyWnd::OnDestroy
5. CmyDlg::OnDestroy
6. CmyWnd::PostNcDestroy
7. CmyWnd的析構
8. CmyDlg::OnDestroy
9. CmyDlg的析構
10. CMyView::PostNcDestroy
11. CmyView的析構
12. CMainFrame的析構
13. CMainFrame::DestroyWindow退出
上面情況是假設我們在CmyWnd和CmyDlg的PostNcDestroy中添加了delete this。如果沒有新增,則7,10不會執行。
因為CView::PostNcDestroy中呼叫了delete this,所以然後會執行CMyView的析構操作。因為CframeWnd::PostNcDestroy中呼叫了delete this,所以最後執行CMainFrame的析構操作。
如果自己的CmyDlg和CmyWnd在PostNcDestroy中有delete this;則二者會被析構。否則記憶體洩漏。當然delete也可以放在CMyView的析構中做,只是不夠OO。
總結
可以有兩種方法銷燬視窗物件對應的視窗和釋放視窗物件指標。一種是通過DestroyWindow。這是比較好的方法,因為最後MFC會自動相應 WM_CLOSE導致CframWnd::DestroyWindow被呼叫,然後會一次釋放所有子視窗的控制代碼。使用者需要做的是在 PostNcDestroy中釋放堆視窗物件指標。但因為某些物件是在棧中申請的,所以delete this可能出錯。這就要保證寫程式時自己建立的視窗儘量使用堆申請。
另一種是delete。Delete一個視窗物件指標有的視窗類(如CWnd,Cdialog)會間接呼叫DestroyWindow,有的視窗類 (如CView,CframeWn)不會呼叫DestroyWindow。所以要小心應對。
二者是相互呼叫的,很繁瑣。
一段很好的文章:(作者:聞怡洋)
一個MFC視窗物件包括兩方面的內容:一是視窗物件封裝的視窗,即存放在m_hWnd成員中的HWND(視窗控制代碼),二是視窗物件本身是一個C++對 象。要刪除一個MFC視窗物件,應該先刪除視窗物件封裝的視窗,然後刪除視窗物件本身。
刪除視窗最直接方法是呼叫CWnd::DestroyWindow或::DestroyWindow,前者封裝了後者的功能。前者不僅會呼叫後者,而 且會使成員m_hWnd儲存的HWND無效(NULL)。如果DestroyWindow刪除的是一個父視窗或擁有者視窗,則該函式會先自動刪除所有的子 視窗或被擁有者,然後再刪除父視窗或擁有者。在一般情況下,在程式中不必直接呼叫DestroyWindow來刪除視窗,因為MFC會自動呼叫 DestroyWindow來刪除視窗。例如,當用戶退出應用程式時,會產生WM_CLOSE訊息,該訊息會導致MFC自動呼叫 CWnd::DestroyWindow來刪除主框架視窗,當用戶在對話方塊內按了OK或Cancel按鈕時,MFC會自動呼叫 CWnd::DestroyWindow來刪除對話方塊及其控制元件。
視窗物件本身的刪除則根據物件建立方式的不同,分為兩種情況。在MFC程式設計中,會使用大量的視窗物件,有些視窗物件以變數的形式嵌入在別的物件內或以 區域性變數的形式建立在堆疊上,有些則用new操作符建立在堆中。對於一個以變數形式建立的視窗物件,程式設計師不必關心它的刪除問題,因為該物件的生命期總是 有限的,若該物件是某個物件的成員變數,它會隨著父物件的消失而消失,若該物件是一個區域性變數,那麼它會在函式返回時被清除。
對於一個在堆中動態建立的視窗物件,其生命期卻是任意長的。初學者在學習C++程式設計時,對new操作符的使用往往不太踏實,因為用new在堆中建立對 象,就不能忘記用delete刪除物件。讀者在學習MFC的例程時,可能會產生這樣的疑問,為什麼有些程式用new建立了一個視窗物件,卻未顯式的用 delete來刪除它呢?問題的答案就是有些MFC視窗物件具有自動清除的功能。
如前面講述非模態對話方塊時所提到的,當呼叫CWnd::DestroyWindow或::DestroyWindow刪除一個視窗時,被刪除視窗的 PostNcDestroy成員函式會被呼叫。預設的PostNcDestroy什麼也不幹,但有些MFC視窗類會覆蓋該函式並在新版本的 PostNcDestroy中呼叫delete this來刪除物件,從而具有了自動清除的功能。此類視窗物件通常是用new操作符建立在堆中的,但程式設計師不必操心用delete操作符去刪除它們,因為 一旦呼叫DestroyWindow刪除視窗,對應的視窗物件也會緊接著被刪除。
不具有自動清除功能的視窗類如下所示。這些視窗物件通常是以變數的形式建立的,無需自動清除功能。
所有標準的Windows控制元件類。
1. 從CWnd類直接派生出來的子視窗物件(如使用者定製的控制元件)。
2. 切分視窗類CSplitterWnd。
3. 預設的控制條類(包括工具條、狀態條和對話條)。
4. 模態對話方塊類。
具有自動清除功能的視窗類如下所示,這些視窗物件通常是在堆中建立的。
1. 主框架視窗類(直接或間接從CFrameWnd類派生)。
2. 檢視類(直接或間接從CView類派生)。
讀者在設計自己的派生視窗類時,可根據視窗物件的建立方法來決定是否將視窗類設計成可以自動清除的。例如,對於一個非模態對話方塊來說,其物件是建立在 堆中的,因此應該具有自動清除功能。
綜上所述,對於MFC視窗類及其派生類來說,在程式中一般不必顯式刪除視窗物件。也就是說,既不必呼叫DestroyWindow來刪除視窗物件封裝 的視窗,也不必顯式地用delete操作符來刪除視窗物件本身。只要保證非自動清除的視窗物件是以變數的形式建立的,自動清除的視窗物件是在堆中建立 的,MFC的執行機制就可以保證視窗物件的徹底刪除。
如果需要手工刪除視窗物件,則應該先呼叫相應的函式(如CWnd::DestroyWindow)刪除視窗,然後再刪除視窗物件.對於以變數形式建立 的視窗物件,視窗物件的刪除是框架自動完成的.對於在堆中動態建立了的非自動清除的視窗物件,必須在視窗被刪除後,顯式地呼叫delete來刪除物件(一 般在擁有者或父視窗的解構函式中進行).對於具有自動清除功能的視窗物件,只需呼叫CWnd::DestroyWindow即可刪除視窗和視窗物件。注 意,對於在堆中建立的視窗物件,不要在視窗還未關閉的情況下就用delete操作符來刪除視窗物件.