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操作符释放。