第162章 マウス・フック


今回は、マウスフックをやります。キーボードフックと ほとんど同じ手法で実現できます。 第101章のマウスキャプチャーより かなり強力です。いろいろなウィンドウのタイトルバーを左クリックすると そのキャプションを変えてしまうというプログラムを作ります。 今回は、dllを暗黙のうちに(明示的でないという意味)ロードします。 扱いはこちらのほうがずっと簡単ですが、dllと呼び出し側のプログラムを 交互に修正していくという作業は結構面倒くさいです。



今回は、dllはdefファイルを用いない方法で作ります。

// hook02x.h #define EXPORT extern "C" __declspec(dllexport) EXPORT int SetMainHWND(HWND); EXPORT BOOL IsHooking(void); EXPORT int MySetHook(void); EXPORT int MyEndHook(void); EXPORT LRESULT CALLBACK MyHookProc(int, WPARAM, LPARAM);

dllのヘッダファイルです。

// hook02x.cpp #include <windows.h> #include "hook02x.h" HINSTANCE hInst; HHOOK hHook; HWND hWnd; BOOL bHook; int WINAPI DllMain(HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) { hInst = hInstance; return TRUE; } EXPORT int SetMainHWND(HWND hMainWindow) { hWnd = hMainWindow; return 0; } EXPORT BOOL IsHooking() { if (bHook) return TRUE; else return FALSE; } EXPORT int MySetHook() { hHook = SetWindowsHookEx(WH_MOUSE, (HOOKPROC)MyHookProc, hInst, NULL); if (hHook == NULL) { MessageBox(hWnd, "SetWindowsHookEx失敗です", "Error", MB_OK); return -1; } else { MessageBox(hWnd, "SetWindowsHookEx成功です", "OK", MB_OK); bHook = TRUE; return 0; } } EXPORT int MyEndHook() { if (UnhookWindowsHookEx(hHook) != 0) { MessageBox(hWnd, "UnhookWindowsHookEx成功です", "OK", MB_OK); bHook = FALSE; return 0; } else { MessageBox(hWnd, "UnhookWindowsHookEx失敗です", "Error", MB_OK); return -1; } } EXPORT LRESULT CALLBACK MyHookProc(int nCode, WPARAM wp, LPARAM lp) { MOUSEHOOKSTRUCT *pmh; pmh = (MOUSEHOOKSTRUCT *)lp; if (nCode < 0) return CallNextHookEx(hHook, nCode, wp, lp); if (wp == WM_NCLBUTTONDOWN) { if (pmh->wHitTestCode != HTCAPTION) return CallNextHookEx(hHook, nCode, wp, lp); MessageBox(pmh->hwnd, "このアプリケーションタイトルを変更します", "変更", MB_OK); if (SetWindowText(pmh->hwnd, "猫でもわかるフック") != 0) MessageBox(pmh->hwnd, "タイトルを変更しました", "成功", MB_OK); else MessageBox(pmh->hwnd, "タイトル変更失敗", "Error", MB_OK); return TRUE; } return CallNextHookEx(hHook, nCode, wp, lp); }

今回はdll側でSetWindowsHookEx関数を実行するのでこのdllのインスタンスハンドルが 必要になります。どうすればいいのかというと実に簡単です。DllMain関数を 書けばよいのです。この関数の第1引数がこのdllのインスタンスハンドルとなります。 これをグローバル変数にコピーしておいて、好きなところで使えばよいですね。

SetMainHWND関数はなくてもよいのですが、これは、呼び出し側の親ウィンドウの ハンドルを知るために作ってみました。メッセージボックスを出すときの親として 使えます。呼び出し側では親ウィンドウが作られたらすぐにこの関数を呼び出します。

IsHooking関数は現在フック中か、そうでないかを呼び出し側にに知らせる関数です。 単にbHookの値を調べているだけです。

MySetHook関数はフックをインストールします。今回はマウス・フックなので SetWindowsHookEx関数の第1引数はWH_MOUSEとなります。成功したら bHookをTRUEにしておきます。

MyEndHook関数はフックを終了させます。成功したらbHookをFALSEにしておきます。

MyHookProcはマウス・フックのプロシージャです。ここが一番大切ですね。 nCodeはキーボード・フックと同じです。もしこれが負の値なら直ちに CallNextHookEx関数の戻り値を返さなくてはいけません。

wpにはマウス・メッセージが入ります。

lpにはMOUSEHOOKSTRUCT構造体へのポインタが入ります。

typedef struct tagMOUSEHOOKSTRUCT { // ms POINT pt; HWND hwnd; UINT wHitTestCode; DWORD dwExtraInfo; } MOUSEHOOKSTRUCT;

ptはマウスの位置を示すPOINT構造体です。このとき位置はスクリーン座標系です。

hwndはマウスメッセージを受け取るウィンドウのハンドルです。

wHitTestCodeはヒットテストコードです。ヒットテストコードに関しては後で 解説します。

dwExtraInfoメッセージ関連のエクストラ情報です。

さて、このプログラムはタイトルバーを左クリックしたとき そのウィンドウのキャプションを変えてしまうというものです。 処理すべきメッセージはWM_NCLBUTTONDOWNです。 マウスがウィンドウ内だがクライアント領域外で動かされたり、クリックされると 非クライアント領域メッセージが送られます。WM_NC**の形をしています。 普通は非クライアント領域メッセージを自分で処理することはありません。

次にメニューなども非クライアント領域なので、タイトルバーのキャプション以外を クリックしたときはメッセージを本来のプロシージャに(または次のフックに) 返してやらなくてはいけません。このときヒットテストコードを調べると どこのクリックなのかがわかります。ヒットコードは「WM_NCHITTESTメッセージの DefWindowProcの戻り値」というのをヘルプで調べれば詳しく載っています。 ちなみにタイトルバーのときはHTCAPTION、最大化ボタンのときはHTMAXBUTTON、 最小化ボタンのときはHTMINBUTTON、メニューのときはHTMENU、クライアント領域 のときはHTCLIENTとなります。

蛇足ですが、WM_NCHITTESTメッセージは、あらゆるクライアント領域、非クライアント 領域メッセージに先立って送られるマウスメッセージです。さて、非クライアント領域 がクリックされてそれがタイトルバーならばSetWindowText関数でキャプションを 変更します。

こうやってみるとdllの中身自体はそれほど難しくないですね。 次に呼び出し側プログラムを見てみましょう。

// hook02.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "終了(&X)", IDM_END END POPUP "フック(&H)" BEGIN MENUITEM "フック開始(&S)", IDM_HOOKSTART MENUITEM "フック終了(&E)", IDM_HOOKEND END END

普通のメニューのリソース・スクリプトです。

// hook02.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include "resource.h" #include "hook02x.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); char szClassName[] = "hook02"; //ウィンドウクラス int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }

いつもとほとんど同じです。ビルドするときはdllを作ったときにできる hook02x.libをリンクするのを忘れないでください。それと dllを作ったときに使ったdll02x.hをincludeするのも忘れないでください。 また、当然dll02x.dllはこのプログラムから見えるところにコピーしておいて下さい。 (普通はdllと呼び出し側のプログラム両方にに少しずつ手を入れながら作るので dll, dllのヘッダファイル、dllを作ったときにできるlibを呼び出し側のディレクトリに コピーするバッチファイルを作っておくと便利です。dllを手直ししてビルド、バッチ ファイルの実行、呼び出し側の手直しとビルド、を繰り返します。)

//ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); }

いつもの通りです。メニュー名を忘れないでください。

//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるフック",//タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 170,//幅 95, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }

いつものとおりですが、デフォルトではウィンドウが大きすぎるので 少し小さめにしてあります。

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; HMENU hMenu; switch (msg) { case WM_INITMENU: hMenu = (HMENU)wp; if (IsHooking()) { EnableMenuItem(hMenu, MF_BYCOMMAND | IDM_HOOKSTART, MF_GRAYED); EnableMenuItem(hMenu, MF_BYCOMMAND | IDM_HOOKEND, MF_ENABLED); } else { EnableMenuItem(hMenu, MF_BYCOMMAND | IDM_HOOKSTART, MF_ENABLED); EnableMenuItem(hMenu, MF_BYCOMMAND | IDM_HOOKEND, MF_GRAYED); } break; case WM_CREATE: SetMainHWND(hWnd); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_HOOKSTART: MySetHook(); break; case IDM_HOOKEND: MyEndHook(); break; } break; case WM_CLOSE: if (IsHooking()) { MessageBox(hWnd, "フックが解除されていません", "注意!", MB_OK); break; } id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }

WM_INITMENUが来ると、dllのIsHooking関数を呼んでフック中かどうかを 調べます。それによってメニューアイテムを使用可能にしたりグレー表示に したりしています。

WM_CREATEが来たときに、dllのSetMainHWNDを呼んでdllにこのウィンドウの ハンドルを教えてやります。こういう使い方はdllに呼び出し側の情報を 伝えるのに重宝します。

あとは、メニューにしたがってMySetHook関数とかMyEndHook関数を 呼んでいるだけです。

今回も簡単でした。せっかくdllに呼び出し側のウィンドウハンドルを 教えているので、呼び出し側のクライアント領域にフック中かどうかを 表示する関数をdll内に作ってみてください。また、メニュー項目の 使用可能、グレー表示などもdll側から行うようにしてみてください。


[SDK第2部 Index] [総合Index] [Previous Chapter] [Next Chapter]

Update 09/Dec/1998 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。