テクニカル ノート 17:ウィンドウ オブジェクトの破棄
このメモでは、メソッドの CWnd::PostNcDestroy 使用方法について説明します。 このメソッドは、CWnd 派生オブジェクトの割り当てをカスタマイズする場合に使用します。 また、このメモでは、演算子の代わりに delete C++ Windows オブジェクトを破棄するために使用CWnd::DestroyWindowする必要がある理由についても説明します。
この記事のガイドラインに従うと、クリーンアップの問題はほとんどありません。 このような問題は、C++ メモリの削除/解放を忘れたり、HWND のようなシステム リソースの解放を忘れたり、オブジェクトを解放する回数が多すぎたりした場合に発生する可能性があります。
問題
各ウィンドウ オブジェクト (CWnd から派生したクラスのオブジェクト) は、C++ オブジェクトと HWND の両方を表します。 C++ オブジェクトはアプリケーションのヒープに割り当てられ、HWND はウィンドウ マネージャーによってシステム リソースに割り当てられます。 ウィンドウ オブジェクトを破棄する方法は複数あるため、システム リソースやメモリのリークを防ぐ一連のルールを用意する必要があります。 また、これらのルールによって、オブジェクトと Windows ハンドルが複数回破棄されるのを防ぐ必要もあります。
ウィンドウの破棄
次の 2 つの許可された方法で、Windows オブジェクトを破棄できます。
CWnd::DestroyWindowまたは Windows APIDestroyWindowを呼び出す。delete演算子を使用して、明示的に削除する。
最初のケースは、最も一般的なものです。 このケースは、コードが直接呼び出 DestroyWindow されない場合でも適用されます。 ユーザーがフレーム ウィンドウを直接閉じると、このアクションによってWM_CLOSE メッセージが生成され、このメッセージに対する既定の応答は呼び出し 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でも、この呼び出しでは、ハンドルがデタッチされNULL、クリーンアップ フェーズ中に無限再帰が発生することはありません。
注意
システムは通常、Windows WM_NCDESTROY メッセージを処理した後に呼び出CWnd::PostNcDestroyし、HWNDC++ ウィンドウ オブジェクトは接続されなくなりました。 また、障害が発生した場合は、ほとんどのCWnd::Create呼び出しの実装でもシステムが呼び出CWnd::PostNcDestroyします。 自動クリーンアップ規則については、この記事の後半で説明します。
自動クリーンアップ クラス
次のクラスは、自動クリーンアップ用に設計されていません。 通常は、他の C++ オブジェクトまたはスタックに埋め込まれます。
すべての標準 Windows コントロール (
CStatic、CEdit、CListBoxなど)。CWndから直接派生した子ウィンドウ (カスタム コントロールなど)。分割ウィンドウ (
CSplitterWnd)。既定のコントロール バー (
CControlBarから派生したクラス。コントロール バー オブジェクトの自動削除の有効化については、「テクニカル ノート 31」を参照)。スタック フレームのモーダル ダイアログ用に設計されたダイアログ (
CDialog)。CFindReplaceDialogを除くすべての標準ダイアログ。ClassWizard によって作成された既定のダイアログ。
次のクラスは自動クリーンアップ用に設計されています。 通常、これらはヒープに自分自身で割り当てられます。
メイン フレーム ウィンドウ (
CFrameWndから直接または間接的に派生)。ビュー ウィンドウ (
CViewから直接または間接的に派生)。
これらの規則を解除する場合は、派生クラスの PostNcDestroy メソッドをオーバーライドする必要があります。 クラスに自動クリーンアップを追加するには、基本クラスを呼び出して、次の操作を delete this;行います。 クラスから自動クリーンアップを削除するには、直接基底クラスの PostNcDestroy メソッドではなく、CWnd::PostNcDestroy を直接呼び出します。
自動クリーンアップ動作の変更を使用する最も一般的な状況は、ヒープに割り当てることができるモードレス ダイアログを作成する場合です。
delete を呼び出すタイミング
DestroyWindow を呼び出して、Windows オブジェクトを C++ メソッドまたはグローバル DestroyWindow API のいずれかで破棄することをお勧めします。
MDI 子ウィンドウを破棄するためにグローバル DestroyWindow API を呼び出さないでください。 代わりに、仮想メソッド CWnd::DestroyWindow を使用する必要があります。
自動クリーンアップを実行しない C++ Window オブジェクトの場合、演算子をdelete使用すると、正しい派生クラスを指していない場合にデストラクターでCWnd::~CWnd呼び出DestroyWindowそうとしたときにメモリ リークが発生する可能性VTBLがあります。 リークは、システムが呼び出す適切な破棄メソッドを見つけることができないために発生します。 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_hWnd 、次になります NULL。 自動クリーンアップ オブジェクトを呼び出 DestroyWindow すと、C++ オブジェクトはなくなり、C++ delete 演算子によって解放されます。これは、自動クリーンアップの PostNcDestroy実装で行われます。