Compartir vía


TN017: Destrucción de objetos de ventana

En esta nota se describe el uso del método CWnd::PostNcDestroy. Use este método si desea realizar la asignación personalizada de objetos derivados de CWnd. En esta nota también se explica por qué debe usar CWnd::DestroyWindow para destruir un objeto de Windows de C++ en lugar del operador delete.

Si sigue las instrucciones de este artículo, tendrá pocos problemas de limpieza. Estos problemas pueden deberse a problemas como olvidarse de eliminar o liberar memoria de C++, olvidarse de liberar recursos del sistema como HWND o liberar objetos demasiadas veces.

El problema.

Cada objeto de Windows (objeto de una clase derivada de CWnd) representa un objeto de C++ y un HWND. Los objetos de C++ se asignan en el montón de la aplicación y el administrador de ventanas asigna HWND en los recursos del sistema. Dado que hay varias maneras de destruir un objeto de ventana, debemos proporcionar un conjunto de reglas que impidan pérdidas de memoria o recursos del sistema. Estas reglas también deben impedir que los objetos y los identificadores de Windows se destruyan más de una vez.

Destrucción de ventanas

A continuación se muestran las dos formas permitidas de destruir un objeto de Windows:

  • Llamar a CWnd::DestroyWindow o a la API de Windows DestroyWindow.

  • Eliminarlo explícitamente con el operador delete.

El primer caso es, en gran medida, el más común. Este caso se aplica incluso si el código no llama a DestroyWindow directamente. Cuando el usuario cierra directamente una ventana de marco, esta acción genera el mensaje WM_CLOSE y la respuesta predeterminada a este mensaje es llamar a DestroyWindow. Cuando se destruye una ventana primaria, Windows llama a DestroyWindow para todos sus elementos secundarios.

El segundo caso, el uso del operador delete en objetos de Windows, suele ser poco frecuente. A continuación se muestran algunos casos en los que el uso de delete es la opción correcta.

Limpieza automática con CWnd::PostNcDestroy

Cuando el sistema destruye una ventana de Windows, el último mensaje de Windows enviado a la ventana es WM_NCDESTROY. El controlador predeterminado CWnd para ese mensaje es CWnd::OnNcDestroy. OnNcDestroy desasociará HWND del objeto de C++ y llamará a la función virtual PostNcDestroy. Algunas clases invalidan esta función para eliminar el objeto de C++.

La implementación predeterminada de CWnd::PostNcDestroy no hace nada y es adecuada para los objetos de ventana que se asignan en el marco de pila o incrustados en otros objetos. Este comportamiento no es adecuado para los objetos de ventana diseñados para la asignación en el montón sin ningún otro objeto. En otras palabras, no es adecuado para los objetos de ventana que no están incrustados en otros objetos de C++.

Las clases diseñadas para la asignación por sí solas en el montón invalidan el método PostNcDestroy para realizar un delete this;. Esta instrucción liberará cualquier memoria asociada al objeto de C++. Aunque el destructor predeterminado CWnd llama a DestroyWindow si m_hWnd no es NULL, esta llamada no conduce a una recursividad infinita porque el identificador se desasociará y NULL durante la fase de limpieza.

Nota:

Normalmente, el sistema llama a CWnd::PostNcDestroy después de procesar el mensaje de Windows WM_NCDESTROY y HWND y el objeto de ventana de C++ ya no están conectados. El sistema también llamará a CWnd::PostNcDestroy en la implementación de la mayoría de las llamadas CWnd::Create si se produce un error. Las reglas de limpieza automática se describen más adelante en este artículo.

Clases de limpieza automática

Las siguientes clases no están diseñadas para la limpieza automática. Normalmente se insertan en otros objetos de C++ o en la pila:

  • Todos los controles estándar de Windows (CStatic, CEdit, CListBox, etc.).

  • Cualquier ventana secundaria derivada directamente de CWnd (por ejemplo, controles personalizados).

  • Ventanas divisoras (CSplitterWnd).

  • Barras de control predeterminadas (clases derivadas de CControlBar; consulte la Nota técnica 31 para habilitar la eliminación automática para objetos de barra de control).

  • Diálogos (CDialog) diseñados para diálogos modales en el marco de pila.

  • Todos los diálogos estándar excepto CFindReplaceDialog.

  • Los diálogos predeterminados creados por ClassWizard.

Las siguientes clases no están diseñadas para la limpieza automática. Normalmente se asignan por sí mismas en el montón:

  • Ventanas de marco principal (derivadas directa o indirectamente de CFrameWnd).

  • Ventanas de visualización (derivadas directa o indirectamente de CView).

Si quiere interrumpir estas reglas, debe invalidar el método PostNcDestroy en la clase derivada. Para agregar la limpieza automática a la clase, llame a la clase base y, a continuación, realice una delete this;. Para quitar la limpieza automática de la clase, llame directamente a CWnd::PostNcDestroy en lugar del método PostNcDestroy de la clase base directa.

El uso más común de cambiar el comportamiento de limpieza automática es crear un cuadro de diálogo de modelado que se pueda asignar en el montón.

Cuándo llamar a delete

Se recomienda llamar a DestroyWindow para destruir un objeto de Windows, ya sea el método de C++ o la API global DestroyWindow.

No llame a la API global DestroyWindow para destruir una ventana secundaria de MDI. Debe utilizar el método virtual CWnd::DestroyWindow en lugar de este.

En el caso de los objetos de Windows de C++ que no realizan la limpieza automática, el uso del operador delete puede provocar una pérdida de memoria al intentar llamar a DestroyWindow en el destructor CWnd::~CWnd si VTBL no apunta a la clase derivada correctamente. La fuga se produce porque el sistema no encuentra el método de destrucción adecuado para llamar. Usar DestroyWindow en lugar de delete evita estos problemas. Dado que este error puede ser sutil, la compilación en modo de depuración generará la siguiente advertencia si está en riesgo.

Warning: calling DestroyWindow in CWnd::~CWnd
    OnDestroy or PostNcDestroy in derived class will not be called

Para los objetos de Windows de C++ que realizan la limpieza automática, debe llamar a DestroyWindow. Si usa el operador delete directamente, el asignador de memoria de diagnóstico de MFC le notificará que está liberando memoria dos veces. Las dos repeticiones son la primera llamada explícita y la llamada indirecta a delete this; en la implementación de limpieza automática de PostNcDestroy.

Después de llamar a DestroyWindow en un objeto que no sea de limpieza automática, el objeto de C++ seguirá existiendo, pero m_hWnd será NULL. Después de llamar a DestroyWindow en un objeto de limpieza automática, el objeto de C++ desaparecerá, liberado por el operador delete de C++ en la implementación de limpieza automática de PostNcDestroy.

Consulte también

Notas técnicas por número
Notas técnicas por categoría