フックには、スレッド固有のフックとグローバルなフックがありますが、今回はグローバルフックを使ってみます。
グローバルフックを使えば、ウインドウズがアプリケーションに送るメッセージを監視することができます。マウスカーソルを自動的に動かしたり、ボタンを勝手に押してくれるようなソフトもフックを使っていると思われます。
グローバルフックのフックプロシージャはDLLに置かないといけないので、グローバルフックを使うプログラムではDLLを作る必要が出てきます。また、DLLはアプリケーション1つにつき1つずつ読み込まれて、それぞれが独自のデータ領域を持つようなのですべてのDLLで共通に使用できる共有領域を作る必要があります。この領域には、フック
プロシージャのハンドルを格納します。これを共有領域に置かないと複数のフックを組み込んだときにうまく動かないことがあります。
フックの種類を示します
WH_CALLWNDPROCフック
SendMessage関数でウィンドウ プロシージャに送られるメッセージを監視する。Windowsは、 送り先のウィンドウ プロシージャにメッセージを渡す前にWH_CALLWNDPROCフック プロシージャを呼び出す。
WH_CALLWNDPROCRETフック
SendMessage関数でウィンドウ プロシージャに送られるメッセージを監視する。Windowsは、 送り先のウィンドウ プロシージャがメッセージを処理した後でWH_CALLWNDPROCRETフック プロシージャを呼び出す。
WH_CBTフック
Windowsは、 次に示す処理を行う前に、 WH_CBTフック プロシージャを呼び出す。
・ ウィンドウのアクティブ化、 作成、 破棄、 最大化、 アイコン化、 移動、 サイズ変更
・ システム メッセージ キューからのマウス イベントやキーボード イベントの削除
・ 入力フォーカスの設定
・ システム メッセージ キューの同期化
WH_DEBUGフック
システムのほかのフックに関連付けられているフック プロシージャを呼び出す前に、 WH_DEBUGフック プロシージャを呼び出す。
WH_FOREGROUNDIDLE
フォアグラウンドスレッドがアイドル状態になったときに呼び出される。
WH_GETMESSAGEフック
GetMessage関数やPeekMessage関数が返そうとしているメッセージを監視する。
WH_JOURNALRECORDフック
入力イベントを監視、 記録する。通常、 アプリケーションは、 このフックを使ってマウス イベントやキーボード イベントのシーケンスを記録し、 後でWH_JOURNALPLAYBACKフックを使って再生する。
WH_JOURNALPLAYBACKフック
システム メッセージ キューにメッセージを挿入する。アプリケーションは、 WH_JOURNALRECORDフックで記録しておいた一連のマウス イベントやキーボード イベントを、 このフックを使って再生できる。WH_JOURNALPLAYBACKフックがインストールされているときは、 通常のマウス入力やキーボード入力は使用不能になる。
WH_KEYBOARDフック
GetMessage関数やPeekMessage関数が返そうとしているWM_KEYDOWNメッセージやWM_KEYUPメッセージの流れを監視する。
WH_MOUSEフック
GetMessage関数やPeekMessage関数が返そうとしているマウス メッセージを監視する。
WH_MSGFILTERフックとWH_SYSMSGFILTERフック
メニューやスクロール バー、 メッセージ ボックス、 ダイアログ ボックスが処理しようとしているメッセージを監視したり、 ユーザーがAlt+TabキーやAlt+Escキーを押したときに別のウィンドウがアクティブ化されようとするのを検出できる。WH_MSGFILTERフックは、 フック プロシージャをインストールしたアプリケーションが作成したメニューやスクロール バー、 メッセージ ボックス、 ダイアログ ボックスに渡されるメッセージしか監視できない。WH_SYSMSGFILTERフックは、 すべてのアプリケーションについてこのようなメッセージを監視できる。
WH_SHELLフック
Windowsシェル アプリケーションが、 重要な通知を取得するときに使う。Windowsは、 シェル アプリケーションがアクティブ化されようとしているときや、 トップ レベル ウィンドウが作成または破棄されるときに、 WH_SHELLフック プロシージャを呼び出す。
それでは、WH_CALLWNDPROCを使ってウインドウが作成されたときにビープ音を鳴らすプログラムを作ってみます。
まずは、実行ファイルのソースです。
#define STRICT
#include <windows.h>
#include "resource.h"
extern void CALLBACK sethook(void);
extern void CALLBACK freehook(void);
static HINSTANCE Instance;
int CALLBACK WndProc(HWND hwnd, unsigned wMessage,
WPARAM wParam, LPARAM lParam)
{
省略です
いつもと変わらない、プロシージャです
}
int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmd, int nCmdShow)
{
前略
ウインドウを作ってください
sethook(); フックを組み込みます
while (GetMessage (&msg, NULL, 0, 0)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
freehook(); フックを解除します。
return msg.wParam ;
}
省略が多いですが、フックの組み込みと解除以外は他のプログラムと同じです。
次がDLLのソースです。
#define STRICT
#include <windows.h>
#define DllExport __declspec( dllexport )
DllExport void CALLBACK sethook(void);
DllExport void CALLBACK freehook(void);
//共有領域
#pragma data_seg(".sharedata")
HHOOK hHookWnd=0; //フック プロシージャのハンドル 共有領域のデータは初期化してないとうまく確保されない。
#pragma data_seg()
HINSTANCE hdll;
//WinMain関数のようなもの 無いと困る。
//VCとBCで関数名が違う。BCではDllEntryPoint
BOOL WINAPI DllMain (HINSTANCE hInstance, DWORD reason, LPVOID lpReserved)
{
(void)lpReserved;
if(reason==DLL_PROCESS_ATTACH){
hdll=hInstance; //DLLのハンドルを保存する
}
return TRUE;
}
LRESULT CALLBACK CallWndProc(int nCode,WPARAM wParam,LPARAM lParam)
{
if(nCode==HC_ACTION){ //メッセージを処理しなければならないかどうか。無くてもいいと思うけど一応
if(((CWPSTRUCT *)lParam)->message==WM_CREATE){ //ウィンドウ作成か?
MessageBeep(0xffffffff); //ビープ
}
}
return CallNextHookEx(hHookWnd, nCode, wParam, lParam); //次のフックを呼ぶ
}
//フックを組み込む
void CALLBACK sethook(void)
{
hHookWnd=SetWindowsHookEx(WH_CALLWNDPROC,CallWndProc, hdll, 0);
}
//フックを解除する
void CALLBACK freehook(void)
{
UnhookWindowsHookEx(hHookWnd);
}
また、今回は共有領域があるのでそれを設定するためにDEFファイルを作る必要があります。拡張子をDEFとしてプロジェクトに組み込んでください。
LIBRARY
SECTIONS
.sharedata READ WRITE SHARED //読み込み・書き込み共有
今回はビープを鳴らせるだけでしたが、特定のウインドウを開けなくしたり、ウインドウの初期位置を変えたり、ボタンを自動的に押したりと、フックを使えば様々なことが出来るようになります。ただ、フックもプログラムなのであまり多く組み込みすぎるとシステム自体が遅くなってしまうので気をつけてください。
このサンプルではDLLの中でビープを鳴らしていますが、本来はフックプロシージャの中でビープを鳴らすのはいけないようです。また、ビープに限らずあまり時間のかかる処理は、フック内で行わずにメインウインドウにメッセージを送って、そこで処理をする方がいいと思います。
98/07/15追加