TN017: Destroying Window Objects
銷毀窗口對象
此章節講述了CWnd::PostNcDestroy成員函數的使用。如果你想對派生自CWnd的對象做自定義的處理,你可以使用這個函數。此章節也闡述了“要銷毀一個C++的Windows對象,使用DestroyWindow而不是delete”這個最重要規則的原因。
這是非常重要的。如果你遵照以下的指導方法,你就會很少碰到清除方面的問題(例如忘記刪除/釋放C++內存,忘記釋放系統資源比如說HWNDs,或者釋放對象太多次)。
問題Windows對象(CWnd派生類的對象)既代表一個C++對象(在應用程序的堆中分配)也代表了一個HWND(由窗口管理器在系統資源里分配)。由于存在多種途徑來銷毀一個窗口對象,我們必須提供一組規則以防止系統資源或者應用程序的內存泄露問題,同時也防止對象和Windows句柄被多次銷毀。
這不僅僅是一個內存管理的問題。一個Windows窗口的存在會對用戶界面產生影響:窗口繪制在屏幕上;一旦它被銷毀,同樣對系統資源也會產生影響。在你的應用程序地址空間里泄漏C++內存并不會像泄漏系統資源那樣糟糕。
銷毀窗口
有兩種方法被允許來銷毀一個Windows對象:
l 調用CWnd::DestroyWindow或Windows API ::DestroyWindow.
l 利用delete操作符來進行明確的刪除工作。
第一種方法是迄今為止最常用的。即使DestroyWindow沒有在你的代碼里被直接調用,此方法也照常適用。這種情況就是,當用戶直接關閉一個框架窗口時(缺省的WM_CLOSE行為主是調用DestroyWindow),當一個父窗口(框架窗口)被銷毀時,Windows會調用DestroyWindow來銷毀它的所有的子窗口。
Auto Cleanup with CWnd::PostNcDestroy
When destroying a Windows window, the last Windows message sent to the window is WM_NCDESTROY. The default CWnd handler for that message (CWnd::OnNcDestroy) will detach the HWND from the C++ object and call the virtual function PostNcDestroy. Some classes override this function to delete the C++ object.
利用CWnd::PostNcDestroy進行自動清除
當銷毀一個Windows窗口時,最后發送給此窗口的Windows消息是WM_NCDESTROY。CWnd對此消息的缺省處理(CWnd::OnNcDestroy)會將C++對象與HWND分離,并調用虛函數PostNcDestroy。一些類重載這個函數來刪除C++對象。
The default implementation of CWnd::PostNcDestroy does nothing which is appropriate for window objects allocated on the stack frame or embedded in other objects. This is not appropriate for window objects that are designed to be allocated by themselves on the heap (not embedded in other C++ object).
CWnd::PostNcDestroy的缺省操作是什么也不做,這適合于那些分配在堆棧或者嵌在其他對象里面的窗口對象。這不適合于那些設計來分配在堆上的窗口對象(不嵌在其他C++對象中)。
Those classes that are designed to be allocated by themselves on the heap override the PostNcDestroymember function to perform a "delete this". This statement will free any C++ memory associated with the C++ object. Even though the default CWnd destructor calls DestroyWindow if m_hWnd is non-NULL, this does not lead to infinite recursion since the handle will be detached and NULL during the cleanup phase.
那些設計來分配在堆上的類可以重載成員函數PostNcDestroy以執行“delete this”操作。它將會釋放任何與此C++對象相關的C++內存。盡管缺省的CWnd析構函數會在m_hWnd不為空的情況下調用DestoryWindow,但這不會導致無窮遞歸,因為此句柄在清除階段將會處于分離狀態并為空。
Note CWnd::PostNcDestroy is normally called after the Windows WM_NCDESTROY message is processed, as part of window destruction, and the HWND and the C++ window object are no longer attached. CWnd::PostNcDestroy will also be called in the implementation of most Create calls if failure occurs (see below for auto cleanup rules).
注意CWnd::PostNcDestroy一般會在Windows消息WM_NCDESTORY處理后被調用,把它作為窗口銷毀的一部分,同時HWND和C++窗口對象不再關聯。
CWnd::PostNcDestroy也會在大部分Create調用的執行部分被調用,如果錯誤發生的話(自動清理規則如下)
Auto Cleanup Classes
The following classes are not designed for auto-cleanup. They are normally embedded in other C++ object or on the stack:
- All the standard Windows controls (CStatic, CEdit, CListBox, and so on).
- Any child windows derived directly from CWnd (for example, custom controls)
- Splitter windows (CSplitterWnd)
- Default control bars (classes derived from CControlBar, see Technical Note 31 for enabling auto-delete for control bar objects)
- Dialogs (CDialog) - designed for modal dialogs on the stack frame
- All the standard dialogs except CFindReplaceDialog
- The default dialogs created by ClassWizard
自動清理類
以下的這些類不是設計來做自動清理的。他們通常嵌在其他C++對象或者在堆棧上:
l 所有的標準Windows控件(CStatic, CEdit, ClistBox等)
l 所有從CWnd直接派生來的子窗口(比例,自定義控件)
l 拆分窗口(CSplitterWnd)
l 缺省控制條(從CcontrolBar派生的類,查看TN31來了解能夠自動刪除的控制條對象)
l 對話框(CDialog)設計來在堆棧上創建模態對話框
l 所有的標準對話框,除了CfindReplaceDialog
l 由ClassWizard創建的缺省對話框
The following classes are designed for auto-cleanup. They are normally allocated by themselves on the heap:
- Main frame windows (derived directly or indirectly from CFrameWnd)
- View windows (derived directly or indirectly from CView)
以下這些類設計來做自動清理。他們一般單獨分配在堆上:
l 主框架窗口(直接或間接派生于CFrameWnd)
l 視圖窗口(直接或間接派生于CView)
If you wish to break any of these rules, you must override the PostNcDestroy member function in your derived class. To add auto-cleanup to your class, simply call your base class and then do a delete this. To remove auto-cleanup from your class, call CWnd::PostNcDestroy directly instead of thePostNcDestroy member in your direct base class.
如果你想打破任何一條規則,你就必須在你的派生類中重載PostNcDestroy成員函數。為了增加自動清理到你的類,只需要調用你的基類并做delete this操作。為了將自動清理從你的類中移出,直接調用CWnd::PostNcDestroy來代替你基類的成員函數PostNcDestory.
The most common use of the above is to create a modeless dialog that can be allocated on the heap.
以上內容常用在創建一個能在堆上分配的非模態的對話框
When to Call 'delete'
The recommended way to destroy a Windows object is to call DestroyWindow, either the C++ member function or the global ::DestroyWindow API.
何時調用delete
銷毀一個窗口最好是調用DestoryWindow,不管是C++的成員函數還是全局的::DestoryWindow API.
Do not call the global ::DestroyWindow API to destroy an MDI Child window, use the virtual member function CWnd::DestroyWindow instead.
不要調用全局的API ::DestroyWindow來銷毀一個MDI子窗口,使用虛擬成員函數CWnd::DestroyWindow來代替它。
For C++ Window objects that don't perform auto-cleanup, using DestroyWindow instead of delete avoids problems of having to call DestroyWindow in the CWnd::~CWnd destructor where the VTBL is not pointing to the correctly derived class. This can lead to subtle bugs so the diagnostic (debug) version of MFC will warn you with
Warning: calling DestroyWindow in CWnd::~CWnd
OnDestroy or PostNcDestroy in derived class will not be called
對于那些不執行自動清理的C++窗口對象,使用DestoryWindow來代替delete以避免你必須在CWnd::~CWnd析構函數中調用DestoryWindow的問題,而在此處VTBL并沒有指向正確的派生類。這可能會導致許多bugs,所以MFC診斷版本(調試)中會警告你:
Warning: calling DestroyWindow in CWnd::~CWnd
OnDestroy or PostNcDestroy in derived class will not be called
In the case of C++ Windows objects that do perform auto-cleanup, you must call DestroyWindow. If you use operator delete directly, the MFC diagnostic memory allocator will alert you that you are freeing memory twice (the first call to delete as well as the indirect call to "delete this" in the auto-cleanup implementation of PostNcDestroy).
對于執行自動清理工作的C++Windows對象,你必須調用DestroyWindow。如果你直接使用操作符delete,MFC的診斷內存分配算符將會警告你:你正在第二次釋放內存(第一次調用delete,還有在PostNcDestroy的自動清理執行過程中調用delete this)。
After calling DestroyWindow on a non-auto-cleanup object, the C++ object will still be around, butm_hWnd will be NULL. After calling DestroyWindow on an auto-cleanup object, the C++ object will be gone, freed by the C++ delete operator in the auto-cleanup implementation of PostNcDestroy..
對于一個不執行自動清理的對象,在調用DestroyWindow之后,這個C++對象仍然存在,但是m_hWnd會為空。對一個執行自動清理的對象,在調用DestroyWindow之后,此C++對象就不存在了,它被PostNcDestroy的自動清理執行過程里的delete操作符釋放。