ウィンドウ プロシージャについて

各ウィンドウは特定のウィンドウ クラスのメンバーです。 ウィンドウ クラスは、個々のウィンドウでそのメッセージを処理するために使用される既定のウィンドウ プロシージャを決定します。 同じクラスに属するすべてのウィンドウでは、同じウィンドウ プロシージャが使用されます。 たとえば、システムによってコンボ ボックス クラス (COMBOBOX) のウィンドウ プロシージャが定義され、すべてのコンボ ボックスでそのウィンドウ プロシージャが使用されます。

通常、アプリケーションは、少なくとも 1 つのウィンドウ クラスとそれに関連付けられたウィンドウ プロシージャを登録します。 クラスを登録すると、アプリケーションは、そのクラスの多数のウィンドウを作成でき、それらはすべて同じウィンドウ プロシージャを使用します。 これは、複数のソースで同じコード部分が同時に呼び出される可能性があることを意味するため、ウィンドウ プロシージャから共有リソースを変更する際は注意が必要です。 詳細については、「ウィンドウ クラス」を参照してください。

ダイアログ ボックスのウィンドウ プロシージャ (ダイアログ ボックス プロシージャと呼ばれます) の構造と機能は通常のウィンドウ プロシージャと同様です。 このセクションでウィンドウ プロシージャに言及しているすべての点は、ダイアログ ボックス プロシージャにも当てはまります。 詳細については、「ダイアログ ボックス」を参照してください。

このセクションでは、次のトピックについて説明します。

ウィンドウ プロシージャの構造

ウィンドウ プロシージャは、4 つのパラメーターを持ち、符号付き値を返す関数です。 パラメーターはウィンドウ ハンドル、UINT メッセージ識別子、WPARAM および LPARAM データ型で宣言された 2 つのメッセンジャー パラメーターで構成されます。 詳細については、WindowProcに関するページを参照してください。

多くの場合、メッセージ パラメーターには、上位と下位の両方のワードに関する情報が含まれます。 メッセージ パラメーターから情報を抽出するためにアプリケーションで使用できるマクロがいくつかあります。 たとえば、LOWORD マクロは、メッセージ パラメーターから下位ワード (ビット 0 から 15) を抽出します。 その他のマクロとしては、HIWORDLOBYTEHIBYTE マクロがあります。

戻り値の解釈は、特定のメッセージによって異なります。 適切な戻り値を決定するには、各メッセージの説明を参照してください。

ウィンドウ プロシージャは再帰的に呼び出すことができるため、プロシージャで使用されるローカル変数の数を最小限抑えることが重要です。 個々のメッセージを処理する場合、アプリケーションはウィンドウ プロシージャの外部で関数を呼び出して、ローカル変数の過度の使用を回避する必要があります。ローカル変数が多すぎると、再帰が深い場合にスタックのオーバーフローが発生する可能性があります。

既定のウィンドウ プロシージャ

既定のウィンドウ プロシージャ関数 DefWindowProc は、すべてのウィンドウで共有される特定の基本的な動作を定義します。 既定のウィンドウ プロシージャは、ウィンドウの最小限の機能を提供します。 アプリケーション定義のウィンドウ プロシージャは、既定の処理のために、プロシージャで処理されないメッセージを DefWindowProc 関数に渡す必要があります。

ウィンドウ プロシージャのサブクラス化

アプリケーションでウィンドウを作成すると、ウィンドウのメッセージを処理するウィンドウ プロシージャのアドレスなど、ウィンドウ固有の情報を格納するためのメモリ ブロックがシステムによって割り当てられます。 システムは、ウィンドウにメッセージを渡す必要がある場合、ウィンドウ固有の情報でウィンドウ プロシージャのアドレスを検索し、そのプロシージャにメッセージを渡します。

"サブクラス化" は、特定のウィンドウに送信または投稿されたメッセージを、ウィンドウで処理される前にアプリケーションでインターセプトして処理できるようにする手法です。 ウィンドウをサブクラス化すると、アプリケーションでウィンドウの動作を拡張、変更、または監視できます。 アプリケーションは、編集コントロールやリスト ボックスなど、システム グローバル クラスに属するウィンドウをサブクラス化できます。 たとえば、アプリケーションで編集コントロールをサブクラス化して、コントロールが特定の文字を受け入れないようにすることができます。 ただし、別のアプリケーションに属するウィンドウまたはクラスをサブクラス化することはできません。 すべてのサブクラス化は、同じプロセス内で実行する必要があります。

アプリケーションは、ウィンドウの元のウィンドウ プロシージャのアドレスを新しいウィンドウ プロシージャ ("サブクラス プロシージャ" と呼ばれます) のアドレスに置き換えてウィンドウをサブクラス化します。 その後は、サブクラス プロシージャが、ウィンドウに送信または投稿されたメッセージを受け取ります。

サブクラス プロシージャは、メッセージを受信すると 3 つのアクションを実行できます。つまり、メッセージを元のウィンドウ プロシージャに渡す、メッセージを変更して元のウィンドウ プロシージャに渡す、またはメッセージを処理して元のウィンドウ プロシージャに渡さないことができます。 サブクラス プロシージャでメッセージを処理する場合、メッセージを元のウィンドウ プロシージャに渡す前、後、または前と後の両方で処理できます。

システムには、インスタンスグローバルの 2 つの種類のサブクラス化が用意されています。 "インスタンス サブクラス化" では、アプリケーションは、ウィンドウの単一インスタンスのウィンドウ プロシージャ アドレスを置き換えます。 アプリケーションで既存のウィンドウをサブクラス化するには、インスタンス サブクラス化を使用する必要があります。 "グローバル サブクラス化" では、アプリケーションは、ウィンドウ クラスの WNDCLASSEX 構造体内のウィンドウ プロシージャのアドレスを置き換えます。 これ以降にクラスで作成されるウィンドウはすべて、サブクラス プロシージャのアドレスを持ちますが、クラスの既存のウィンドウは影響を受けません。

インスタンス サブクラス化

アプリケーションは、SetWindowLongPtr 関数を使用してウィンドウのインスタンスをサブクラス化します。 アプリケーションは、GWL_WNDPROC フラグ、サブクラス化するウィンドウへのハンドル、サブクラス プロシージャのアドレスを SetWindowLongPtr に渡します。 サブクラス プロシージャは、アプリケーションの実行可能ファイルまたは DLL のいずれかに配置できます。

GWL_WNDPROC フラグが渡されたとき、SetWindowLongPtr はウィンドウの元のウィンドウ プロシージャのアドレスを返します。 アプリケーションはそのアドレスを保存する必要があります。それを以降の CallWindowProc 関数の呼び出しで使用して、インターセプトしたメッセージを元のウィンドウ プロシージャに渡します。 また、アプリケーションには、サブクラスをウィンドウから削除するために、元のウィンドウ プロシージャのアドレスも必要です。 サブクラスを削除するには、アプリケーションで SetWindowLongPtr をもう一度呼び出して、元のウィンドウ プロシージャのアドレスを GWL_WNDPROC フラグおよびハンドルと共にウィンドウに渡します。

システム グローバル クラスはシステムによって所有されており、コントロールの側面はシステムのバージョンごとに変更される可能性があります。 アプリケーションで、システム グローバル クラスに属するウィンドウをサブクラス化する必要がある場合、開発者は、システムの新しいバージョンがリリースされたときに、そのアプリケーションを更新することが必要な場合があります。

インスタンス サブクラス化は、ウィンドウの作成後に行われるため、余分なバイトをウィンドウに追加することはできません。 ウィンドウをサブクラス化するアプリケーションは、ウィンドウのプロパティ リストを使用して、サブクラス化されたウィンドウのインスタンスに必要なデータを格納する必要があります。 詳細については、「ウィンドウ プロパティ」を参照してください。

アプリケーションで、サブクラス化されたウィンドウをサブクラス化する場合、実行された順序とは逆の順序でサブクラスを削除する必要があります。 逆の順序で削除しないと、回復不能なシステム エラーが発生するおそれがあります。

グローバル サブクラス化

ウィンドウ クラスをグローバルにサブクラス化するには、アプリケーションにクラスのウィンドウへのハンドルが必要です。 また、サブクラスを削除するためのハンドルも必要です。 ハンドルを取得するには、通常、アプリケーションで、サブクラス化されるクラスの非表示ウィンドウを作成します。 ハンドルを取得したら、アプリケーションで、ハンドル、GCL_WNDPROC フラグ、サブクラス プロシージャのアドレスを指定して、SetClassLongPtr 関数を呼び出します。 SetClassLongPtr から、クラスの元のウィンドウ プロシージャのアドレスが返されます。

元のウィンドウ プロシージャのアドレスは、グローバル サブクラス化でも、インスタンス サブクラス化で使用されるのと同じ方法で使用されます。 サブクラス プロシージャでは、CallWindowProc を呼び出して、メッセージを元のウィンドウ プロシージャに渡します。 アプリケーションは、元のウィンドウ プロシージャのアドレス、GCL_WNDPROC フラグ、サブクラス化されたクラスのウィンドウへのハンドルを指定し、SetClassLongPtr をもう一度呼び出して、ウィンドウ クラスからサブクラスを削除します。 コントロール クラスをグローバルにサブクラス化するアプリケーションでは、アプリケーションが終了したときにサブクラスを削除する必要があります。そうしなければ、回復不能なシステム エラーが発生するおそれがあります。

グローバル サブクラス化には、インスタンス サブクラス化と同じ制限に加えて、いくつかの追加の制限があります。 元のウィンドウ プロシージャで余分なバイトがどのように使用されるかを正確に把握せずに、アプリケーションでそれらのバイトをクラスおよびウィンドウ インスタンスに使用することはできません。 アプリケーションでデータをウィンドウに関連付ける必要がある場合、ウィンドウ プロパティを使用する必要があります。

ウィンドウ プロシージャのスーパークラス化

"スーパークラス化" は、既存のクラスの基本機能に加えて、アプリケーションによって提供される拡張機能を備えた新しいウィンドウ クラスをアプリケーションで作成できるようにする手法です。 スーパークラスは、"基底クラス" と呼ばれる既存のウィンドウ クラスに基づきます。 多くの場合、基底クラスは、編集コントロールなどのシステム グローバル ウィンドウ クラスですが、任意のウィンドウ クラスにすることもできます。

スーパークラスには、スーパークラス プロシージャと呼ばれる独自のウィンドウ プロシージャがあります。 "スーパークラス プロシージャ" は、メッセージを受信すると 3 つのアクションを実行できます。つまり、メッセージを元のウィンドウ プロシージャに渡す、メッセージを変更して元のウィンドウ プロシージャに渡す、またはメッセージを処理して元のウィンドウ プロシージャに渡さないことができます。 スーパークラス プロシージャでメッセージを処理する場合、メッセージを元のウィンドウ プロシージャに渡す前、後、または前と後の両方で処理できます。

サブクラス プロシージャとは異なり、スーパークラス プロシージャでは、ウィンドウ作成メッセージ (WM_NCCREATEWM_CREATE など) を処理できますが、基底クラス ウィンドウ プロシージャで初期化プロシージャを実行できるように、それらのメッセージを元の基底クラス ウィンドウ プロシージャにも渡す必要があります。

ウィンドウ クラスをスーパークラス化するには、アプリケーションで、まず、GetClassInfoEx 関数を呼び出して、基底クラスに関する情報を取得します。 GetClassInfoEx は、WNDCLASSEX 構造体を、基底クラスの WNDCLASSEX 構造体の値で埋めます。 次に、アプリケーションで、アプリケーション独自のインスタンス ハンドルを WNDCLASSEX 構造体の hInstance メンバーにコピーし、サブクラスの名前を lpszClassName メンバーにコピーします。 基底クラスにメニューがある場合、アプリケーションでは、同じメニュー識別子を持つ新しいメニューを提供し、メニュー名を lpszMenuName メンバーにコピーする必要があります。 スーパークラス プロシージャで WM_COMMAND メッセージを処理し、それを既定クラスのウィンドウ プロシージャに渡さない場合、メニューには、対応する識別子は必要ありません。 GetClassInfoEx から、lpszMenuNamelpszClassNameWNDCLASSEX 構造体の hInstance メンバーは返されません。

さらに、アプリケーションでは、WNDCLASSEX 構造体の lpfnWndProc メンバーを設定する必要もあります。 GetClassInfoEx 関数は、このメンバーをクラスの元のウィンドウ プロシージャのアドレスで埋めます。 アプリケーションはこのアドレスを保存して、メッセージを元のウィンドウ プロシージャに渡した後、スーパークラス プロシージャのアドレスを lpfnWndProc メンバーにコピーする必要があります。 アプリケーションでは、必要に応じて、WNDCLASSEX 構造体の他のメンバーを変更できます。 アプリケーションは、WNDCLASSEX 構造体を埋めた後、その構造体のアドレスを RegisterClassEx 関数に渡してスーパークラスを登録します。 その後、スーパークラスを使用してウィンドウを作成できます。

スーパークラス化によって新しいウィンドウ クラスが登録されるため、アプリケーションでは、余分なクラス バイトと余分なウィンドウ バイトの両方を追加できます。 インスタンス サブクラスまたはグローバル サブクラスで使用してはならないのと同じ理由で、スーパークラスでも、基底クラスまたはウィンドウに元の余分なバイトを使用することはできません。 また、アプリケーションで、クラスまたはウィンドウ インスタンスのいずれかに使用するために余分なバイトを追加する場合、元の基底クラスで使用される余分なバイト数を基準にして余分なバイトを追加する必要があります。 基底クラスで使用されるバイト数は基底クラスのバージョンごとに異なるため、スーパークラス独自の余分なバイトの開始オフセットも、基底クラスのバージョンごとに異なる場合があります。