Share via


TN017:销毁窗口对象

本说明介绍 CWnd::PostNcDestroy 方法的使用。 如果要进行 CWnd派生对象的自定义分配,请使用此方法。 本说明还解释为何应使用 CWnd::DestroyWindow 销毁 C++ Windows 对象而不是 delete 运算符。

如果遵循本文中的准则,那么你几乎不会遇到清理问题。 这些问题可能是由于忘记删除/释放 C++ 内存、忘记释放系统资源(如 HWND)或释放对象次数太多等问题所造成的。

问题

每个窗口对象(派生自 CWnd 的类的对象)都同时表示一个 C++ 对象和一个 HWND。 C++ 对象在应用程序的堆中进行分配,而 HWND 由窗口管理器在系统资源中进行分配。 由于可通过多种方法销毁窗口对象,因此必须提供一组规则来防止系统资源或内存泄漏。 这些规则还必须防止多次销毁对象和窗口句柄。

销毁窗口

以下是销毁窗口对象的两种允许方法:

  • 调用 CWnd::DestroyWindow 或 Windows API DestroyWindow

  • 使用 delete 运算符显式删除。

第一种情况是迄今为止最常见的。 即使代码未直接调用 DestroyWindow,此情况也适用。 当用户直接关闭框架窗口时,此操作会生成 WM_CLOSE 消息,对此消息的默认响应是调用 DestroyWindow。 当父窗口销毁时,Windows 会为其所有子窗口调用 DestroyWindow

第二种情况(即对窗口对象使用 delete 运算符)十分少见。 下面是使用 delete 是正确选择的一些情况。

使用 CWnd::PostNcDestroy 进行自动清理

当系统销毁 Windows 窗口时,发送到窗口的最后一个 Windows 消息是 WM_NCDESTROY。 该消息的默认 CWnd 处理程序是 CWnd::OnNcDestroyOnNcDestroy 会将 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 控件(CStaticCEditCListBox 等)。

  • 任何直接从 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 的自动清理实现中释放)。

另请参阅

按编号列出的技术说明
按类别列出的技术说明