次の方法で共有


TN017: ウィンドウ オブジェクトの破棄

このメモでは、 CWnd::PostNcDestroy メソッドの使用方法について説明します。 CWnd派生オブジェクトの割り当てをカスタマイズする場合は、このメソッドを使用します。 また、delete演算子ではなく、CWnd::DestroyWindowを使用して C++ Windows オブジェクトを破棄する必要がある理由についても説明します。

この記事のガイドラインに従うと、クリーンアップの問題はほとんどありません。 これらの問題は、C++ メモリの削除/解放の忘れ、 HWNDなどのシステム リソースの解放の忘れ、オブジェクトの解放回数が多すぎるなどの問題が原因で発生する可能性があります。

問題

各 windows オブジェクト ( CWnd から派生したクラスのオブジェクト) は、C++ オブジェクトと HWNDの両方を表します。 C++ オブジェクトはアプリケーションのヒープに割り当てられ、 HWNDはウィンドウ マネージャーによってシステム リソースに割り当てられます。 ウィンドウ オブジェクトを破棄するにはいくつかの方法があるため、システム リソースまたはメモリ リークを防ぐ一連のルールを提供する必要があります。 これらの規則では、オブジェクトと Windows ハンドルが複数回破棄されないようにする必要もあります。

ウィンドウの破棄

Windows オブジェクトを破棄するには、次の 2 つの方法を使用できます。

  • CWnd::DestroyWindowまたは Windows API DestroyWindowの呼び出し。

  • delete演算子を使用して明示的に削除する。

最初のケースが最も一般的です。 このケースは、コードが DestroyWindow を直接呼び出さない場合でも適用されます。 ユーザーがフレーム ウィンドウを直接閉じると、このアクションによってWM_CLOSE メッセージが生成され、このメッセージに対する既定の応答は DestroyWindowを呼び出します。 親ウィンドウが破棄されると、Windows はすべての子に対して DestroyWindow を呼び出します。

2 番目のケースは、Windows オブジェクトで delete 演算子を使用することはまれです。 deleteを使用することが正しい選択である場合を次に示します。

自動クリーンアップ CWnd::PostNcDestroy

システムが Windows ウィンドウを破棄すると、ウィンドウに送信された最後の Windows メッセージは WM_NCDESTROY。 そのメッセージの既定の CWnd ハンドラーは CWnd::OnNcDestroyOnNcDestroy は、C++ オブジェクトから HWND をデタッチし、仮想関数 PostNcDestroyを呼び出します。 一部のクラスでは、この関数をオーバーライドして C++ オブジェクトを削除します。

CWnd::PostNcDestroyの既定の実装では何も行われません。これは、スタック フレームに割り当てられているウィンドウ オブジェクトや他のオブジェクトに埋め込まれているウィンドウ オブジェクトに適しています。 この動作は、他のオブジェクトのないヒープ上の割り当て用に設計されたウィンドウ オブジェクトには適していません。 つまり、他の C++ オブジェクトに埋め込まれていないウィンドウ オブジェクトには適していません。

ヒープでの割り当て専用に設計されたクラスは、delete this;を実行するためにPostNcDestroy メソッドをオーバーライドします。 このステートメントは、C++ オブジェクトに関連付けられているすべてのメモリを解放します。 既定のCWndデストラクターの呼び出しDestroyWindowm_hWndNULLされていない場合でも、この呼び出しは、クリーンアップ フェーズ中にハンドルがデタッチされ、NULLされるため、無限再帰につながりません。

通常、システムは Windows WM_NCDESTROY メッセージを処理した後にCWnd::PostNcDestroyを呼び出し、HWNDと C++ ウィンドウ オブジェクトは接続されなくなりました。 障害が発生した場合、システムはほとんどのCWnd::Create呼び出しの実装でCWnd::PostNcDestroyを呼び出します。 自動クリーンアップ規則については、この記事の後半で説明します。

自動クリーンアップ クラス

次のクラスは、自動クリーンアップ用に設計されていません。 通常、これらは他の C++ オブジェクトまたはスタックに埋め込まれます。

  • すべての標準 Windows コントロール (CStaticCEditCListBoxなど)。

  • CWndから直接派生した子ウィンドウ (カスタム コントロールなど)。

  • 分割ウィンドウ (CSplitterWnd)。

  • 既定のコントロール バー ( CControlBarから派生したクラス。コントロール バー オブジェクトの自動削除を有効にする 場合のテクニカル ノート 31 を参照してください)。

  • スタック フレーム上のモーダル ダイアログ用に設計されたダイアログ (CDialog)。

  • CFindReplaceDialogを除くすべての標準ダイアログ。

  • ClassWizard によって作成された既定のダイアログ。

次のクラスは、自動クリーンアップ用に設計されています。 通常、これらはヒープ上で自分で割り当てられます。

  • メイン フレーム ウィンドウ ( CFrameWndから直接または間接的に派生)。

  • ウィンドウの表示 ( CViewから直接または間接的に派生)。

これらの規則を解除する場合は、派生クラスの PostNcDestroy メソッドをオーバーライドする必要があります。 クラスに自動クリーンアップを追加するには、基底クラスを呼び出し、 delete this;を実行します。 クラスから自動クリーンアップを削除するには、直接基底クラスのPostNcDestroy メソッドではなく、CWnd::PostNcDestroyを直接呼び出します。

自動クリーンアップ動作を変更する最も一般的な用途は、ヒープに割り当てることができるモードレス ダイアログを作成することです。

呼び出すタイミング delete

DestroyWindowを呼び出して、C++ メソッドまたはグローバル DestroyWindow API のいずれかの Windows オブジェクトを破棄することをお勧めします。

MDI 子ウィンドウを破棄するためにグローバル DestroyWindow API を呼び出さないでください。 代わりに仮想メソッド CWnd::DestroyWindow を使用する必要があります。

自動クリーンアップを実行しない C++ Window オブジェクトの場合、delete演算子を使用すると、VTBLが正しく派生したクラスを指していない場合、CWnd::~CWndデストラクターでDestroyWindowを呼び出そうとすると、メモリ リークが発生する可能性があります。 リークは、システムが呼び出す適切な destroy メソッドを見つけることができないために発生します。 deleteの代わりにDestroyWindowを使用すると、これらの問題を回避できます。 このエラーは微妙な場合があるため、デバッグ モードでコンパイルすると、リスクがある場合は次の警告が生成されます。

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

自動クリーンアップを実行する C++ Windows オブジェクトの場合は、 DestroyWindowを呼び出す必要があります。 delete演算子を直接使用すると、MFC 診断メモリ アロケーターから、メモリを 2 回解放すると通知されます。 2 つの出現箇所は、最初の明示的な呼び出しと、PostNcDestroyの自動クリーンアップ実装でdelete this;する間接呼び出しです。

自動クリーンアップ以外のオブジェクトに対して DestroyWindow を呼び出した後も、C++ オブジェクトはそのまま残りますが、 m_hWndNULL。 自動クリーンアップ オブジェクトで DestroyWindow を呼び出すと、 PostNcDestroyの自動クリーンアップ実装で C++ delete 演算子によって C++ オブジェクトが解放されます。

こちらも参照ください

番号別のテクニカル ノート
カテゴリ別のテクニカル ノート