TN017:销毁窗口对象
本说明介绍 CWnd::PostNcDestroy
方法的使用。 如果要进行 CWnd
派生对象的自定义分配,请使用此方法。 本说明还解释为何应使用 CWnd::DestroyWindow
销毁 C++ Windows 对象而不是 delete
运算符。
如果遵循本文中的准则,那么你几乎不会遇到清理问题。 这些问题可能是由于忘记删除/释放 C++ 内存、忘记释放系统资源(如 HWND
)或释放对象次数太多等问题所造成的。
问题
每个窗口对象(派生自 CWnd
的类的对象)都同时表示一个 C++ 对象和一个 HWND
。 C++ 对象在应用程序的堆中进行分配,而 HWND
由窗口管理器在系统资源中进行分配。 由于可通过多种方法销毁窗口对象,因此必须提供一组规则来防止系统资源或内存泄漏。 这些规则还必须防止多次销毁对象和窗口句柄。
销毁窗口
以下是销毁窗口对象的两种允许方法:
调用
CWnd::DestroyWindow
或 Windows APIDestroyWindow
。使用
delete
运算符显式删除。
第一种情况是迄今为止最常见的。 即使代码未直接调用 DestroyWindow
,此情况也适用。 当用户直接关闭框架窗口时,此操作会生成 WM_CLOSE 消息,对此消息的默认响应是调用 DestroyWindow
。 当父窗口销毁时,Windows 会为其所有子窗口调用 DestroyWindow
。
第二种情况(即对窗口对象使用 delete
运算符)十分少见。 下面是使用 delete
是正确选择的一些情况。
使用 CWnd::PostNcDestroy
进行自动清理
当系统销毁 Windows 窗口时,发送到窗口的最后一个 Windows 消息是 WM_NCDESTROY
。 该消息的默认 CWnd
处理程序是 CWnd::OnNcDestroy
。 OnNcDestroy
会将 HWND
从 C++ 对象拆离并调用虚拟函数 PostNcDestroy
。 某些类会替代此函数以删除 C++ 对象。
CWnd::PostNcDestroy
的默认实现不执行任何操作,这适用于在堆栈帧上分配或是嵌入在其他对象中的窗口对象。 此行为不适用于为在没有任何其他对象的堆上进行分配而设计的窗口对象。 换句话说,它不适用于未嵌入在其他 C++ 对象中的窗口对象。
为在堆上单独分配而设计的类会替代 PostNcDestroy
方法以执行 delete this;
。 此语句会释放与 C++ 对象关联的任何内存。 即使默认 CWnd
析构函数在 m_hWnd
不为 NULL
时调用 DestroyWindow
,此调用也不会导致无限递归,因为句柄会在清理阶段拆离并为 NULL
。
注意
在处理 Windows WM_NCDESTROY
消息和 HWND
,并且 C++ 窗口对象不再连接之后,系统通常会调用 CWnd::PostNcDestroy
。 如果发生失败,系统还会在大多数 CWnd::Create
调用的实现中调用 CWnd::PostNcDestroy
。 本文后面会将介绍自动清理规则。
自动清理类
以下类不是为自动清理而设计的。 它们通常嵌入到其他 C++ 对象或堆栈中:
所有标准 Windows 控件(
CStatic
、CEdit
、CListBox
等)。任何直接从
CWnd
派生的子窗口(例如自定义控件)。拆分器窗口 (
CSplitterWnd
)。默认控件条(从
CControlBar
派生的类,请参阅技术说明 31 以了解如何为控件条对象启用自动删除)。为堆栈帧上的有模式对话框设计的对话框 (
CDialog
)。所有标准对话框(
CFindReplaceDialog
除外)。ClassWizard 创建的默认对话框。
以下类是为自动清理而设计的。 它们通常由自己在堆上进行分配:
主框架窗口(直接或间接派生自
CFrameWnd
)。视图窗口(直接或间接派生自
CView
)。
如果要打破这些规则,则必须在派生类中替代 PostNcDestroy
方法。 若要将自动清理添加到类,请调用基类,然后执行 delete this;
。 若要从类中移除自动清理,请直接调用 CWnd::PostNcDestroy
而不是直接基类的 PostNcDestroy
方法。
更改自动清理行为的最常见用途是创建可在堆上分配的无模式对话框。
何时调用 delete
建议调用 DestroyWindow
以销毁窗口对象(C++ 方法或全局 DestroyWindow
API)。
不要调用全局 DestroyWindow
API 来销毁 MDI 子窗口。 应改为使用虚拟方法 CWnd::DestroyWindow
。
对于不执行自动清理的 C++ 窗口对象,如果在 VTBL
未指向正确派生的类的情况下尝试在 CWnd::~CWnd
析构函数中调用 DestroyWindow
,则使用 delete
运算符可能会导致内存泄漏。 发生泄漏是因为系统找不到要调用的适当销毁方法。 使用 DestroyWindow
而不是 delete
可避免这些问题。 由于此错误可能很微妙,因此如果面临风险,在调试模式下进行编译会生成以下警告。
Warning: calling DestroyWindow in CWnd::~CWnd
OnDestroy or PostNcDestroy in derived class will not be called
对于执行自动清理的 C++ 窗口对象,必须调用 DestroyWindow
。 如果直接使用 delete
运算符,则 MFC 诊断内存分配器会通知你会两次释放内存。 进行两次释放是在 PostNcDestroy
的自动清理实现中对 delete this;
的首次显式调用和间接调用。
对非自动清理对象调用 DestroyWindow
后,C++ 对象仍会存在,但 m_hWnd
会为 NULL
。 对自动清理对象调用 DestroyWindow
后,C++ 对象会消失(由 C++ 删除运算符在 PostNcDestroy
的自动清理实现中释放)。