最終更新 2004 02/19
サンプルのダウンロード → API_SetWindowsHookEx.lzh(128k)
【 サンプルソースの使い方 】
このサンプルを実行するには
1.「フックをしかけるウィンドウのプロジェクト」
2.「フックしたメッセージを受け取るウィンドウのプロジェクト」
3.「フックを実行するためのDLLのプロジェクト」
以上の3つのプロジェクトを作成する必要があります。
1.と2.は「Win32 Application」でプロジェクトを作成しますが
3.は「Win32 Dynamic-Link Library」で作成することに注意して下さい。
プロジェクト名はなんでも構いませんが、サンプルと同じ名前に
しておく方が無難だと思います。
プロジェクトを作成したら、それぞれ main.cpp というソースファイルを
新規で作成してプロジェクトに追加します。
main.cpp を作成したら、それぞれのプロジェクトのソースをコピーして
ビルドします。
【 ビルドと実行手順 】
まず最初に3.のDLLを作成してビルドします。
ビルドしてできたDLLを2.のプロジェクトの main.cpp と同じフォルダに
コピーします。
念のため、2.の実行ファイルと同じフォルダにもコピーしておくと良いです。
1.と2.はどちらを先にビルドしても構いません。
実行するときは、1.の実行ファイルを先に起動します。
その後、2.の実行ファイルを起動します。
【 補足 】
1.のウィンドウはフックをしかけたいだけのウィンドウなので、ウィンドウを
表示する以外は特に何も処理を行いません。
サンプルでは分かりやすいように説明文を表示しているだけです。
なので、1.のウィンドウはクラス名かウィンドウのタイトルバーの文字さえ
分かってしまえば、どんなアプリケーションでも構いません。
面倒なら1.のサンプルは使わなくても動かすことはできます。
ただ、1.のサンプルを使わない場合は、2.のサンプルの上の方で #define を
している TARGET_CLASS と TARGET_TITLE の値を、フックしたいアプリケーションの
クラス名とタイトルバーに表示される文字に書き換える必要があります。
1.「フックをしかけるウィンドウのプロジェクト」の main.cpp
( プロジェクト名:API_SetWindowsHookEx1 )
2.「フックしたメッセージを受け取るウィンドウのプロジェクト」の main.cpp
( プロジェクト名:API_SetWindowsHookEx2 )
3.「フックを実行するためのDLLのプロジェクト」の main.cpp
( プロジェクト名:API_SetWindowsHookEx_DLL )
SetWindowsHookEx 関数
UnhookWindowsHookEx 関数
CallNextHookEx 関数
対応しているバージョン
95, 98, Me, NT3.1以降, 2000, XP
使用するヘッダとライブラリ
winuser.h
user32.lib
|
【 メッセージフック? 】
VC++ についてくる Spy++ や、スクリーンセーバー、またはキーボードに入力した
文字の記録を取るソフトなどを作るには、メッセージフックというものを使います。
ゲームの支援ツールなどでもよく使われます。(ゲーム中の特定のコマンドをマウス
の右クリックに割り当てるなどのツール)
メッセージフックとは、ウィンドウにメッセージが送られて来た時に、釣り針の
ようなものでメッセージをひっかけて取ってしまうという意味から、こう呼ばれる
そうです。
メッセージフックにはローカルフックとグローバル(システム)フックの2種類が
あり、ローカルフックは自分のウィンドウのメッセージをフックすることで
グローバルフックは、ウィンドウズ全体、あるいは自分以外のウィンドウの
メッセージをフックすることを言います。
ローカルフックはまず使わないと思います。
自分のウィンドウのメッセージはいくらでもウィンドウプロシージャで取って
これますから・・・。
ここではグローバルフックの使い方のみ説明します。
【 メッセージフックに使うAPI関数 】
メッセージフックを仕掛けるには、SetWindowsHookEx() 関数を使います。
仕掛けたフックを解除するには UnhookWindowsHookEx() 関数を使います。
仕掛けたフックはプログラムを終了する前にかならず解除しなければいけません。
【 SetWindowsHookEx() 関数について 】
グローバルフックを仕掛けるには次のように呼び出します。
HHOOK hHook = SetWindowsHookEx(
フックするメッセージの種類,
フックしたメッセージを処理する関数のポインタ,
DLLのインスタンスハンドル,
フックを仕掛けるウィンドウのスレッドID
);
最初の引数 フックするメッセージの種類 には、フックしたメッセージを
どう処理するかの値を指定します。(指定できる値は MSDN ライブラリ参照)
フックしたメッセージを取得して、メッセージの内容を変更したいときには
WH_GETMESSAGE を指定します。
2番目の引数 フックしたメッセージを処理する関数のポインタ は、そのままの
意味です。
サンプルのように
LRESULT CALLBACK HookProc( int nCode, WPARAM wParam, LPARAM lParam )
|
のように宣言して、この関数名を指定します。
この関数内ではフックしたメッセージの処理を行います。
関数名の HookProc は自分の好きな名前にしても構いません。
MyMsgProc という関数名にした場合は、2番目の引数に MyMsgProc と指定すれば
よいことになります。
3番目の引数 DLLのインスタンスハンドル には、DLLのメイン関数に
渡ってくるインスタンスハンドルをそのまま指定します。
4番目の引数 フックを仕掛けるウィンドウのスレッドID には、フックを
仕掛けたいウィンドウのスレッドIDを指定します。
ウィンドウズ全体のメッセージをフックしたい場合は 0 を指定します。
フックを仕掛けたいウィンドウのスレッドIDを取得するには
次のようにします。
//hWnd にフックを仕掛けたいウィンドウの
//ウィンドウハンドルを格納しておく
DWORD threadId = GetWindowThreadProcessId( hWnd, NULL );
|
スレッドIDが何か?については、長くなるのでここでは説明しません。
ここではどうやって取って来るかが分かれば問題はないです。
【 グローバルフックのコツ 】
グローバルフックを行う上で重要な点は
1.フックの処理はDLL側で行う
2.フックの処理に使う変数は共有メモリに配置する
3.DLLから呼び出す関数には extern "C" をつける
4.フックしたメッセージは呼び出し元にそのまま送信する
以上の4点です。
いちいちDLLを作らなければならないのが少々めんどうですが、DLLで
処理を行わないと動いてくれません。
まず、1.「フックの処理はDLL側で行う」ですが、フックを仕掛ける処理や
解除する処理、フックを仕掛けたウィンドウから取ってきたメッセージを処理する
関数など、全てDLL側で処理を行います。
次に2.「フックの処理に使う変数は共有メモリに配置する」ですが、DLLの
インスタンスハンドル、フックハンドル、フックしたメッセージを送信するウィンドウ
(DLLの呼び出し元)のハンドルは共有メモリに配置します。
共有メモリ?といわれてもピンと来ませんし、私自身いまのところ、何故
こうするのかがよく分かっていません。
ただ、こうしないと動きません。
共有メモリに配置した変数は、DLL側と呼び出し側のアプリケーションの
どちらからも同じアドレスを見れば参照できる・・・というような意味なので
しょうか・・・。
共有というからには、何らかの形でDLLと呼び出し元で連携ができるような
値なんだろうと推測します。
//共有メモリを宣言
#pragma data_seg( ".sharedata" )
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;
#pragma data_seg()
//共有メモリを設定
#pragma comment( linker, "/SECTION:.sharedata,RWS" )
|
#pragma data_seg( ".名前" ) 〜 #pragma data_seg() で、共有メモリに
配置する変数を宣言します。
共有メモリに配置する変数は必ず初期化しないといけません。
そのあと、#pragma comment( linker, 〜略〜 ) で、共有メモリを設定します。
.sharedata の . 以下は自分の好きな名前に変更可能です。
3.「DLLから呼び出す関数には extern "C" をつける」については
DLLから呼び出したい関数に extern "C" を付けないで関数の宣言を行うと
外部からDLLの中にある関数を呼び出すのがかなり面倒になります。
詳しい説明は省略しますが、これを防ぐ為に extern "C" をつけます。
extern "C" というのは、C の規則でコンパイルするという意味です。
C++ の規則でコンパイルされると都合が悪いときに、この修飾子を
つけるときがままあります。
今回もそのようなケースです。
またDLLから呼び出したい関数は、プロトタイプ宣言するときに、頭に
__declspec( dllexport ) と付けないと外部から呼び出すことができません。
//もしコンパイラが C++ 対応なら、以下の部分では関数宣言の頭に
//extern "C" を付けて、C の規則でコンパイルする
#ifdef __cplusplus
extern "C"{
#endif
//プロトタイプ宣言
//DLLの外部から以下の2つの関数を呼び出すことができる
__declspec( dllexport ) bool SetHook( HWND, DWORD );
__declspec( dllexport ) bool ResetHook();
#ifdef __cplusplus
}
#endif
//ここまで C の規則でコンパイルされます
|
4.「フックしたメッセージを呼び出し元にそのまま送信する」については
これは必ずしもこうする必要はないのですが、処理が簡単で使いまわしが利く
DLLを作れるので、このようにしてみては?という提案をしているような
感じです。
サンプルソースの3.では、SetHook() 関数の引数で、フックしたメッセージを
送り返すウィンドウのハンドルと、フックしたいウィンドウのスレッドIDを
指定します。
フックしたいウィンドウに送られたメッセージを捕まえると、まず HookProc() が
呼び出されます。
そうしたら、HookProc() 関数の中で使えるメッセージかどうかを判断して、使える
メッセージならフックしたメッセージを送り返すウィンドウに、捕まえたメッセージを
そのまま送信します。
そうするとフックしたメッセージを送り返すウィンドウ、つまり、サンプルソース2.
のウィンドウの WinProc() が呼び出されます。
実際はサンプルソース1.に送信されたメッセージなのに、サンプルソース2.の
ウィンドウに送信されたかのように、そのメッセージを扱うことができます。
【 フックしたいウィンドウを探すには? 】
ウィンドウズ全体のメッセージをフックする場合はいいのですが、例えばメモ帳を
起動していて、そのメモ帳のメッセージをフックしたい場合、どうやってメモ帳の
ウィンドウハンドルを取得すればいいのでしょうか。
その方法は
「現在起動しているアプリケーションのウィンドウハンドルを取得する」
で説明しているので、そちらをご覧下さい。
【 DLLの関数を呼び出して使う方法 】
DLLを読み込んで、DLLにある関数を実行する方法は DLLの作成と使い方 で
説明しているので、そちらをご覧下さい。
|