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

更新 : 2007 年 11 月

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

このトピックのガイドラインに従うと、後処理に関する問題がほとんど発生しません。このような問題は、C++ メモリの削除または解放を忘れたり、HWND などのシステム リソースの解放を忘れたり、オブジェクトの解放を行い過ぎたときに発生する可能性があります。

問題

各ウィンドウ オブジェクト (CWnd からの派生したクラスのオブジェクト) は、C++ オブジェクトと HWND の両方を表します。C++ のオブジェクトはアプリケーションのヒープ上に割り当てられ、HWND はウィンドウ マネージャによってシステム リソースに割り当てられます。ウィンドウ オブジェクトの破棄方法は数とおりあるので、規則を設けないと、システム リソースやメモリのリークが発生する可能性があります。さらに、オブジェクトや Windows のハンドルが複数回破棄されるおそれもあります。

ウィンドウの破棄

ウィンドウ オブジェクトを破棄するには、次の 2 つの方法が認められています。

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

  • delete 演算子で明示的に削除する。

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

ウィンドウ オブジェクトに対して delete 演算子を使う 2 番目の方法は、めったに使用されません。以下では、delete の使用が適している場合について説明します。

CWnd::PostNcDestroy による自動後処理

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

CWnd::PostNcDestroy は、既定では何の処理も行いません。この動作は、スタック フレーム上に割り当てられているウィンドウ オブジェクトにも、他のオブジェクトに埋め込まれているウィンドウ オブジェクトにも適しています。ただし、他のオブジェクトとは独立して、ヒープ上に割り当てられるようにデザインされたウィンドウ オブジェクトには適しません。言い換えると、他の C++ オブジェクトに埋め込まれていないウィンドウ オブジェクトには適しません。

ヒープ上に単独で割り当てるようにデザインされたクラスでは、PostNcDestroy メソッドをオーバーライドして delete this を実行します。このステートメントは、C++ オブジェクトに関連付けられているメモリをすべて解放します。CWnd の既定のデストラクタでは、m_hWnd が NULL 以外の場合に DestroyWindow を呼び出しますが、これが無限再帰になることはありません。ハンドルはデタッチされ、後処理中には NULL になるためです。

5zba4hah.alert_note(ja-jp,VS.90).gifメモ :

システムは、通常、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++ のメソッドでもグローバル API の DestroyWindow でもかまいません。

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

自動後処理を行わない C++ のウィンドウ オブジェクトでは、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 重に解放していることが検出されます。最初の明示的な呼び出しと、PostNcDestroy の自動後処理の実装による delete this の間接呼び出しで 2 回になります。

自動破棄を行わないオブジェクトに対して DestroyWindow を呼び出すと、C++ オブジェクトは依然として存在しますが、m_hWnd は NULL になります。自動破棄を行うオブジェクトに対して DestroyWindow を呼び出すと、C++ オブジェクトは PostNcDestroy の自動破棄処理の C++ の演算子 delete によって破棄されます。

参照

その他の技術情報

番号順テクニカル ノート

カテゴリ別テクニカル ノート