フックのしくみ

 さて、フックです。通常ユーティリティアプリケーションで使用するのは「システムフック」ですが、その前に、フックを理解するために「ローカルフック」について見てみようと思います。

メッセージの流れ
メッセージの流れ  MFCユーザーもいると思うので、まずメッセージの流れについて見てみましょう。
 まずメッセージは、ウィンドウズのシステムから送られてきます。キー入力やマウス操作、SendMessage()等を使用して送られてくるメッセージはすべてシステムからによるものです。
 送られてきたメッセージは、一部を除いてメッセージキューと呼ばれるものに順番に蓄えられます。キューはひとつのスレッドにつきひとつだけ存在するので、スレッドが持つすべてのウィンドウ宛のメッセージがごちゃ混ぜになって積み重ねられることになります。

 ここまでは規定のシステムで、何もしなくても普通にプログラムを組めば行えるものですが、ここからは実際にプログラムを組まなければならない部分です。
 メッセージキューに蓄えられたメッセージは、メッセージループによって古いものから順番に取得されます。メッセージループは単純な半永久ループwhile等を用いてGetMessage()PeekMessage()というAPIを繰り返し処理するだけです。例えば、次のようなコードを使用します。


	MSG	stMsg;

	while( GetMessage( &stMsg, NULL, 0, 0 ) )
	{
		TranslateMessage( &stMsg );
		DispatchMessage( &stMsg );
	}
	

 GetMessage()はキューからメッセージを取得し、MSG構造体へと格納します。これを半永久的に繰り返すのが、メッセージループの役割です。

 キューから取り出したメッセージはTranslateMessage()によって処理されたあと、DispatchMessage()によって、メッセージの送り先となるウィンドウが持つ関数、ウィンドウプロシージャへと転送されます。

 ウィンドウプロシージャは、ウィンドウクラス登録時に特定のウィンドウへと結びつけられる関数のことで、例えば次のような関数を用意します。


LRESULT CALLBACK WndProc( HWND p_hWnd, UINT p_uiMsg, WPARAM wParam, LPARAM lParam )
{
	// メッセージを処理します。
	switch( p_uiMsg )
	{
	case WM_DESTROY:	//×ボタンが押されたとき。
		PostQuitMessage( 0 );	//アプリケーションを終了させます。
		return 0;
	}

	return DefWindowProc( p_hWnd, p_uiMsg, wParam, lParam );
}
	

 ウィンドウプロシージャはウィンドウごとに与えられる、メッセージを処理するための関数です。DispatchMessage()に渡されたメッセージはこのウィンドウプロシージャへと送られます。また、システムから送られてくるメッセージの一部(例えばSendMessage()を用いて送られたもの)は、メッセージキューを介さず直接プロシージャへと送られます

 このような手順でウィンドウプロシージャはメッセージを受け取り、プログラマーはそのための処理をプログラムすることになります。MFCではメッセージループを内部に持ち、メッセージは「メッセージハンドラ」と呼ばれる関数へと送られ、そのなかに処理を組み込むことになります。

 この辺の話は「MFCユーザーのためのAPI講座」の中でそれなりに詳しく書いてあるので、そちらもご覧ください。

フックとは
 このメッセージの流れを見れば分かるとおり、プログラミングする部分はわずかです。基本的には、メッセージループとウィンドウプロシージャを組むだけです。そして逆に言えば、それ以外の部分に手を出すことができないということでもあります。
 例えば特定のキー入力やマウスクリックを無効化したいときなどにはどうすればいいのでしょう。ウィンドウプロシージャの中で処理を組む場合、複数の種類のウィンドウが存在したら、そのすべてのプロシージャで処理しなければならなくなります。また、組み込みコントロール(ボタンとか)は、プロシージャを内部に持つため、プログラムで変更することができません(「サブクラス化」すればできますが、それはのちほど)。
 さらに、MFCを使うにしても、SDKにしても、メッセージループの中で処理を行うのは難しいところがあります。メッセージループはプログラムの中でももっとも深い部分ですので、この部分に手を加えたらどんな影響が出てくるのか心配です。

フック  このような問題に対して、違うアプローチとして存在するのがフックです。
 「フック(Hook)」とは英語で「引っかけるもの」という意味です。「ハンガーのフック」とか「ブラジャーのホック」とかを想像すると分かりやすいでしょう。
 プログラム上のフックは、前述のメッセージの流れの中から、メッセージを引っかけて拾い上げてしまうというものです。さらに、このメッセージを破棄したり、他のウィンドウへ送ったりということもできます。
 拾い上げたフックは、フックをセットしたときに指定した関数、フックプロシージャへと送られます。これはウィンドウプロシージャとほぼ同じもので、このフックプロシージャで横取りしたメッセージを処理することができます。

 この便利さゆえ、フックはしばしばアプリケーションに致命的なダメージを与えることがあります。そうならないようできる限り回避するため、フックにはいくつか種類があり、拾えるメッセージや拾う場所の違う色々な種類のフックが用意されています。

フックの種類
 フックにはまずローカルフックグローバルフック(システムフック)の2週類があります。

 ローカルフックは、自分のスレッドのみに働くフックのことです。それほど使い道はありませんが、モーダレスダイアログをモーダルダイアログに見せかけたり、作製されたダイアログのボタン等を手当たり次第取得したい場合などに使用します。
 グローバルフックは別名システムフックと呼ばれ、すべてのスレッドにセットされ働きます。システムフックはすべてのウィンドウのすべてのメッセージを横取りすることができるという、まさにユーティリティアプリケーションにうってつけの機能です。その分危険度が多く、バグがシステム全体に影響を与えてしまいます。

 このふたつははっきりと目的が違います。ローカルフックは自分のアプリケーションで特殊な処理を行いたいときに使用し、システムフックはユーティリティアプリケーションでシステムをコントロールしたいときに使用します。
 ですが、基本的なシステム、そして手順はほとんど同じです。違う点はただひとつ、システムフックはDLLになければならないという点だけです。

 このふたつとは別に、フックには様々な種類があります。以下に主なフックを表としてまとめておきます(筆者は以下の各フックについて詳しくはありません。実際に使用するときには、各フックプロシージャのリファレンスをよく読んでください)

フックタイプ 拾うメッセージ 特徴
WH_CALLWNDPROC すべてのメッセージ ウィンドウプロシージャが
呼ばれる直前を取得できます。
WH_CALLWNDPROCRET すべてのメッセージ ウィンドウプロシージャが
呼ばれたあとに取得します。
WH_CBT ウィンドウが作られたり
アクティブになったり
ウィンドウの作製・消滅等を
監視できます。
WH_DEBUG そのときどきの
フックプロシージャによる
他のフックプロシージャを監視
して、フックをデバッグできます。
WH_GETMESSAGE すべてのメッセージ GetMessage()のメッセージを
取得できます。
WH_JOURNALPLAYBACK すべてのメッセージ キー入力等の操作を置き換えて、
任意の操作を行うことができます。
WH_JOURNALRECORD すべてのメッセージ キー入力等の操作を監視して
記録することができます。
WH_KEYBOARD キーボード入力 GetMessage()が取得したキー入力を
横取りすることができます。
WH_MOUSE マウス操作 GetMessage()が取得したマウス
メッセージを横取りできます。
WH_MSGFILTER すべてのメッセージ メニュー操作等の特別な
メッセージを取得できます。
WH_SHELL システム関係 システムやシェルに関わる
情報を取得できます。
WH_SYSMSGFILTER メニューなど特定の
メッセージ
WH_MSGFILTERのシステム
フック専用版です。

 以上のフックを目的に合わせて使い分けることが重要です。

つづく
 というわけで、概念の説明はここまで。次回からは実際にフックを使用して、どんな感じになるのか見てみましょう。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.