Compartilhar via


TN017: Destruindo objetos de janela

Esta observação descreve o uso do método CWnd::PostNcDestroy. Use esse método se quiser fazer a alocação personalizada de objetos derivados de CWnd. Esta observação também explica por que você deve usar CWnd::DestroyWindow para destruir um objeto C++ do Windows em vez do operador delete.

Se você seguir as diretrizes neste artigo, terá alguns problemas de limpeza. Esses problemas podem resultar de problemas como esquecer de excluir/liberar memória C++, esquecer de liberar recursos do sistema, como HWNDs, ou liberar objetos muitas vezes.

O problema

Cada objeto de janela (objeto de uma classe derivada de CWnd) representa um objeto C++ e um HWND. Os objetos C++ são alocados no heap do aplicativo e HWNDs são alocados em recursos do sistema pelo gerenciador de janelas. Como há várias maneiras de destruir um objeto de janela, precisamos fornecer um conjunto de regras que impeçam vazamentos de recursos ou memória do sistema. Essas regras também precisam impedir que objetos e identificadores do Windows sejam destruídos mais de uma vez.

Destruindo janelas

Veja as seguintes duas maneiras permitidas de destruir um objeto do Windows:

  • Chamar CWnd::DestroyWindow ou a API do Windows DestroyWindow.

  • Excluir explicitamente com o operador delete.

O primeiro caso é de longe o mais comum. Esse caso se aplica mesmo que seu código não chame DestroyWindow diretamente. Quando o usuário fecha diretamente uma janela do quadro, essa ação gera a mensagem WM_CLOSE e a resposta padrão para essa mensagem é chamar DestroyWindow. Quando uma janela pai é destruída, o Windows chama DestroyWindow para todos os filhos dela.

O segundo caso, o uso do operador delete em objetos do Windows, deve ser raro. Veja a seguir alguns casos em que usar delete é a opção correta.

Limpeza automática com CWnd::PostNcDestroy

Quando o sistema destrói uma janela do Windows, a última mensagem do Windows enviada para a janela é WM_NCDESTROY. O manipulador padrão CWnd dessa mensagem é CWnd::OnNcDestroy. OnNcDestroy desanexará o HWND do objeto C++ e chamará a função virtual PostNcDestroy. Algumas classes substituem essa função para excluir o objeto C++.

A implementação padrão de CWnd::PostNcDestroy não faz nada, o que é apropriado para objetos de janela alocados no registro de ativação ou inseridos em outros objetos. Esse comportamento não é apropriado para objetos de janela projetados para alocação no heap sem outros objetos. Em outras palavras, não é apropriado para objetos de janela que não estão inseridos em outros objetos C++.

Classes projetadas apenas para alocação no heap substituem o método PostNcDestroy para executar um delete this;. Essa instrução liberará qualquer memória associada ao objeto C++. Embora o destruidor padrão CWnd chame DestroyWindow se m_hWnd não for NULL, essa chamada não levará à recursão infinita porque o identificador será desanexado e NULL durante a fase de limpeza.

Observação

O sistema geralmente chama CWnd::PostNcDestroy depois que processa a mensagem WM_NCDESTROY do Windows, e o HWND e o objeto C++ do Windows não estão mais conectados. O sistema também chamará CWnd::PostNcDestroy na implementação da maioria das chamadas CWnd::Create se ocorrer falha. As regras de limpeza automática são descritas posteriormente neste artigo.

Classes de limpeza automática

As classes a seguir não são projetadas para limpeza automática. Normalmente, elas são inseridas em outros objetos C++ ou na pilha:

  • Todos os controles padrão do Windows (CStatic, CEdit, CListBox e assim por diante).

  • Todas as janelas filho derivadas diretamente de CWnd (por exemplo, controles personalizados).

  • Janelas divisoras (CSplitterWnd).

  • Barras de controle padrão (classes derivadas de CControlBar, confira a Nota Técnica 31 para habilitar a exclusão automática para objetos da barra de controle).

  • Caixas de diálogo (CDialog) projetadas para caixas de diálogo modais no registro de ativação.

  • Todas as caixas de diálogo padrão, exceto CFindReplaceDialog.

  • As caixas de diálogo padrão criadas por ClassWizard.

As classes a seguir são projetadas para limpeza automática. Eles normalmente são alocados por si mesmos no heap:

  • Janelas de quadro principal (derivadas direta ou indiretamente de CFrameWnd).

  • Exibir janelas (derivadas direta ou indiretamente de CView).

Se você quiser quebrar essas regras, precisará substituir o método PostNcDestroy em sua classe derivada. Para adicionar a limpeza automática à sua classe, chame sua classe base e faça um delete this;. Para remover a limpeza automática de sua classe, chame CWnd::PostNcDestroy diretamente em vez do método PostNcDestroy de sua classe base direta.

O uso mais comum da alteração do comportamento de limpeza automática é criar uma caixa de diálogo sem janela restrita que possa ser alocada no heap.

Quando chamar delete

Recomendamos que você chame DestroyWindow para destruir um objeto do Windows, o método C++ ou a API global DestroyWindow.

Não chame a API global DestroyWindow para destruir uma janela filho do MDI. Você deve usar o método virtual CWnd::DestroyWindow em vez disso.

Para objetos da Janela C++ que não executam a limpeza automática, o uso do operador delete pode causar um vazamento de memória quando você tenta chamar DestroyWindow no destruidor CWnd::~CWnd se VTBL não apontar para a classe derivada corretamente. O vazamento ocorre porque o sistema não pode encontrar o método de destruição apropriado para chamar. Usar DestroyWindow em vez de delete evita esses problemas. Como esse erro pode ser sutil, a compilação no modo de depuração gerará o aviso a seguir se você estiver em risco.

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

Para objetos do Windows C++ que executam a limpeza automática, você precisa chamar DestroyWindow. Se você usar o operador delete diretamente, o alocador de memória de diagnóstico MFC notificará você de que você está liberando memória duas vezes. As duas ocorrências são a sua primeira chamada explícita e a chamada indireta para delete this; na implementação de limpeza automática de PostNcDestroy.

Depois que você chamar DestroyWindow em um objeto de limpeza não automática, o objeto C++ ainda estará por perto, mas m_hWnd será NULL. Depois que você chamar DestroyWindow em um objeto de limpeza automática, o objeto C++ será apagado, liberado pelo operador de exclusão C++ na implementação de limpeza automática de PostNcDestroy.

Confira também

Observações técnicas por número
Observações técnicas por categoria