Share via


テクニカル ノート 17:ウィンドウ オブジェクトの破棄

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

この記事のガイドラインに従うと、いくつかのクリーン問題が発生します。 このような問題は、C++ メモリの削除/解放を忘れたり、HWND のようなシステム リソースの解放を忘れたり、オブジェクトを解放する回数が多すぎたりした場合に発生する可能性があります。

問題

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

ウィンドウの破棄

次の 2 つの許可された方法で、Windows オブジェクトを破棄できます。

  • CWnd::DestroyWindow または Windows API DestroyWindow を呼び出す。

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

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

delete 演算子を Windows オブジェクトで使用する 2 番目のケースは、あまりありません。 delete を使用することが適切な選択であるいくつかのケースを以下に示します。

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

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

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

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

Note

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

自動クリーンup クラス

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

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

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

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

  • 既定のコントロール バー (CControlBar から派生したクラス。コントロール バー オブジェクトの自動削除の有効化については、「テクニカル ノート 31」を参照)。

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

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

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

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

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

  • ビュー ウィンドウ (CView から直接または間接的に派生)。

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

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

delete を呼び出すタイミング

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

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

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

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

自動クリーンup を実行する C++ Windows オブジェクトの場合DestroyWindowは、.. 演算子を delete 直接使用する場合、MFC 診断メモリ アロケーターは、メモリを 2 回解放していることを通知します。 2 つの出現は、最初の明示的な呼び出しと、自動クリーンup 実装での間接呼び出delete this;しですPostNcDestroy

自動クリーンup 以外のオブジェクトを呼び出DestroyWindowすと、C++ オブジェクトは引き続き存在しますがm_hWnd、次の値になりますNULL。 自動クリーンup オブジェクトを呼び出DestroyWindowすと、C++ オブジェクトはなくなり、自動クリーンup 実装PostNcDestroyの C++ delete 演算子によって解放されます。

関連項目

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