1998/12/11
Visual C++/MFC Tips No.4

メッセージフック


他のアプリケーションに送られているメッセージを監視したいと考えたことがあると思います。
しかし、Win32環境では、普通は他のプロセスへ送られているメッセージを知ることはできません。

そこで使うのが、SetWindowsHookExというAPIです。
このAPIを使えば、メッセージフックを実現できます。
定義は、次のようになっています。

HHOOK SetWindowsHookEx(int nFilterType, HOOKPROC hkprc, HINSTANCE hMod, DWORD dwTreadID)
nFilterType
フィルタ関数のタイプや、アプリケーションへ送られる途中で、フィルタへ迂回されるメッセージの種類を指定します。
いくつか種類がありますが、ここではWH_CALLWNDPROCを使います。
hkprc
フックプロシージャへのポインタです。
dwThreadIDに0か、他のプロセスのスレッドの識別子を指定する場合は、DLL内のフックプロシージャへのポインタを指定しなければなりません
hMod
hkprcパラメータのポイントするフックプロシージャの入っているDLLのインスタンスハンドルを指定します。
dwThreadID
フックプロシージャを関連づけるスレッド識別子を指定します。
0を指定すると、既存のすべてのスレッドにフックプロシージャが関連づけられます。ただし、そのときにはDLL内に定義されているフックプロシージャでなければなりません
戻り値
成功したときには、フックプロシージャのハンドルが、そうでなければNULLが返されます。

フックを解除するときには、UnhookWindowsHookExを使います。

BOOL UnhookWindowsHookEx(HHOOK hhook)

引数hhookには、SetWindowsHookExが返すハンドルを渡します。


実装方法を簡単に説明します。

DLLを作って、その中にフックプロシージャを記述します。 例えば、次のようになります。

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	LPCWPSTRUCT pcwps;

	pcwps = (LPCWPSTRUCT)lParam;

	if(nCode == HC_ACTION)
	{
		//何らかの処理
	}

	return CallNextHookEx(hHook, nCode, wParam, lParam);	// hHook : 保存しておいたフックプロシージャのハンドル
}

注意しなければならないのは、フックプロシージャがプロセスをまたがって動くということです。
本体アプリケーションへの通知が必要になった場合、そこで本体アプリケーションとのプロセス間通信が必要になるわけです。
私は、アプリケーションのウィンドウにメッセージを送ったりしています。
ミューテックスオブジェクトなどの同期オブジェクトを使った同期を考えなければいけないことがありますが、ここではとりあげません。

フックプロシージャが別のプロセスで動くため、フックプロシージャを実行中にグローバル変数をアクセスするときには、よく考えなければなりません。
ここでは、hHookがグローバル変数です。

ここで、アプリケーションがロードされ実行されるときに、Windowsが何をしているのかを、簡単に説明します。
WindowsはマルチタスクのOSです。そのため、1つのプログラムがいろいろなプロセスで同時に実行されていることがあり得ます。
しかし、プログラムコードをプロセス毎に用意しては、メモリが無駄になります。
そこで、データ領域だけをプロセス毎に用意し、プログラムコードは共有します。プログラムコードは共通だからです。
DLLも例外ではありません。
そして、フックプロシージャはプロセスにまたがって動きます。 ですから、フックプロシージャからDLLのデータ領域(例えばhHook)にアクセスするときには、 別のプロセスの領域にアクセスすることになり、データを共有領域に置かないとうまくいきません。

ハンドルを保存するためのhHookは共有領域に置きたいので、DLLのコードに次のような記述をします。

#pragma data_seg(".sharedata")
HHOOK hHook = NULL;			// フックプロシージャのハンドル
#pragma data_seg()
さらに、DLLのDEFファイルに次のような記述をして下さい。DEFファイルが無ければ、作成し、プロジェクトに追加して下さい。
LIBRARY

SECTIONS
    .sharedata READ WRITE SHARED

次は、フックプロシージャのインストールです。
私は、フックの開始をする関数をDLL内部においています。例えば、HookStart()という関数をDLLに用意して、アプリケーション本体から呼んでいます。

void HookStart()
{
	hHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, hInstance, 0);
}

などとします。実際には、成功か失敗かを返す方がいいでしょうね。
ここで、hInstanceとあるのは、DLLのインスタンスハンドルです。


最後は、フックプロシージャのアンインストールです。

void HookEnd()
{
	UnhookWindowsHookEx(hHook);
}

最後に、注意点です。
フックプロシージャ内で、あまり時間のかかる処理はしてはいけません。
時間のかかる処理は、アプリケーション本体のプロセスで行って下さい。

参考文献:「Windows95 APIバイブル (Windows95 Win32 Programing API Bible)1 Win32編」


back
Visual C++ Laboratoryのページへ戻る


back最初のページへ戻る


E-Mail:wbs03748@mail.wbs.ne.jp
質問・感想などのメールはこちらへ。


Copyright(C) 1998 Shingo Nakagawa / e-mail:wbs03748@mail.wbs.ne.jp