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 WindowsDestroyWindow
.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
.