第161章 キーボード・フック


さて、今回は前章に引き続き「キーボード・フック」をやります。 前章で作ったプログラムは自分のアプリケーション内でしか フックが効きませんでした。今回はフックプロシージャをdllにして フックの効果をシステム全体に及ぼします。しかし、使い方によっては システムに重大な影響を及ぼすので必要がなくなったら直ちに フックを解除して下さい。
今回はフック関係のプログラムをdll内に書きます。呼び出し側の プログラムは暗黙のdll呼び出しを行うことにします。

では、dllの方から見てみることにします。

// hookdll.cpp dll #include <windows.h> #include "hookdll.h" #pragma data_seg("MY_DATA") HHOOK hMyHook = 0; #pragma data_seg() HINSTANCE hInst; BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD dwReason, LPVOID lpReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: hInst = hInstDLL; MessageBox(NULL, "アタッチしました", "OK", MB_OK); break; case DLL_PROCESS_DETACH: MessageBox(NULL, "デタッチしました", "OK", MB_OK); break; } return TRUE; } int SetHook() { hMyHook = SetWindowsHookEx(WH_KEYBOARD, MyHookProc, hInst, 0); if (hMyHook == NULL) MessageBox(NULL, "フック失敗", "dll", MB_OK); else { MessageBox(NULL, "フック成功", "dll", MB_OK); } return 0; } int ResetHook() { if (UnhookWindowsHookEx(hMyHook) != 0) MessageBox(NULL, "フック解除成功", "OK", MB_OK); else MessageBox(NULL, "フック解除失敗", "Error", MB_OK); return 0; } LRESULT CALLBACK MyHookProc(int nCode, WPARAM wp, LPARAM lp) { char str[256]; if (nCode < 0) return CallNextHookEx(hMyHook, nCode, wp, lp); if (wp >= 0x30 && wp <= 0x39) { wsprintf(str, "hMyHook = %d", hMyHook); MessageBox(NULL, str, "MyHookProc", MB_OK); return CallNextHookEx(hMyHook, nCode, wp, lp); } wsprintf(str, "キー入力はインターセプトされました\n フックハンドル= %d", hMyHook); MessageBox(NULL, str, "インターセプト", MB_OK); return TRUE; }

dllのソースです。

最初の方にある#pragma...というのは後で説明します。 DllMain関数については第124章を参照してください。 SetHook関数はフックをセットして、ResetHook関数はフックを解除する関数です。 MyHookProcはプロシージャですが、ここで数値キー(テンキーではない)以外は メッセージボックスでキー入力がインターセプトされたことを知らせて、このdllを アタッチしているプロセスにはメッセージが届きません。数値キーが押されると フックハンドルを知らせてからメッセージが届きます。

// hook01x.h LRESULT CALLBACK MyHookProc(int, WPARAM, LPARAM); int SetHook(); int ResetHook();

ヘッダファイルです。このdllを呼び出すプログラムでも使います。

; hookdll.def LIBRARY hookdll SECTIONS MY_DATA READ WRITE SHARED EXPORTS MyHookProc @1 SetHook @2 ResetHook @3

モジュール定義ファイルです。SECTIONSでこのように定義しておくとソースの

#pragma data_seg("MY_DATA")

.....

#pragma data_seg()

で囲まれた部分のグローバル変数がプロセス間で共有されます。 (ただしグローバル変数を初期化していないと無効) これは、ちょっと旧式の方法(32ビット版VC++では定義ファイルの設定は すべてリンカオプションでまかなえる)ですが、使い方が簡単で便利な側面も あります。dllができたらこれを呼び出すプログラムを作ればよいわけですが これは簡単です。

// hookmain.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

呼び出し側プログラムのリソース・スクリプトです。単なるメニューです。

// hookmain.cpp #ifndef STRICT #define STRICT #endif #include <windows.h> #include "resource.h" #include "hookdll.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); char szClassName[] = "hookmain"; //ウィンドウクラス BOOL bHook = FALSE;

dllを作ったときつかったhookdll.hをインクルードするのを忘れないでください。 また、dllを作ったときにできたhookdll.libをプロジェクトに参加させるのを 忘れないでください。

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; } //ウィンドウ・クラスの登録 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座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ 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; static HMENU hMenu; static HINSTANCE hInst; switch (msg) { case WM_CREATE: hMenu = GetMenu(hWnd); EnableMenuItem(hMenu, IDM_HOOKEND, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_HOOKSTART, MF_BYCOMMAND | MF_ENABLED); break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_HOOKSTART: EnableMenuItem(hMenu, IDM_HOOKSTART, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_HOOKEND, MF_BYCOMMAND | MF_ENABLED); SetHook(); bHook = TRUE; break; case IDM_HOOKEND: EnableMenuItem(hMenu, IDM_HOOKEND, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_HOOKSTART, MF_BYCOMMAND | MF_ENABLED); ResetHook(); bHook = FALSE; break; } break; case WM_CLOSE: if (bHook) { 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; }

メニューからIDM_HOOKSTARTが選択されると、フックが始まり IDM_HOOKENDが選択されるとフックが解除されます。

フック状態の時「メモ帳」などを起動してキー入力を行ってみてください。 キー入力をしたときにアタッチされることがわかります。「メモ帳」を 終了するとデタッチすることがわかります。

では、dll内でフックハンドルをプロセス間で共有しないとどうなるでしょうか。 「メモ帳」を起動してキー入力を行うとフックは起こりますがhMyHookの値は ゼロとなります。

次に、このdllのプログラムはDllMainで取得したインスタンスハンドルを SetWindowsHookEx関数で使っています。SetHook関数の引数を変更して SetHook(HINSTANCE hInst)として呼び出し側のインスタンスハンドルを 使うとどうなるでしょうか。実はこうするとhookmain内でのみフックが起こり 他のプロセスではフックが起こりません。

hookmain.exeを複数起動して、それぞれでフックを行うとどうなるでしょうか。 いろいろ試してみてください。


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

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