第160章 メッセージフックの基礎


フックについてはすでに、第40章 でやりました。本来のプロシージャに届くはずのメッセージを 先に引っ掛けて取ってしまいます。取ったままにしてもよいし、 何らかの処理をしてから本来のプロシージャに返してもよいです。 簡単なものから解説します。今回は必要ありませんが、dllの作り方を 忘れた人は今のうちに、第119章第122章あたりを読んで復習しておいて ください。



フックを開始するにはSetWindowsHookEx関数を使います。 不要になったらUnhookWindowsHookEx関数を使います。

HHOOK SetWindowsHookEx( int idHook, // インストールするフックのタイプ HOOKPROC lpfn, // フックプロシージャのアドレス HINSTANCE hMod, // インスタンスハンドル DWORD dwThreadId // スレッドID );

idHookには、フックのタイプを指定します。今回は 比較的簡単なWH_KEYBOARDを指定します。これは、キー操作 メッセージをフックすることを意味します。 この他に、WH_GETMESSAGE, WH_MOUSE, WH_MSGFILTERなど があります。

lpfnにはフックプロシージャのポインタを指定します。

hModには、フックプロシージャが入っているDLLのインスタンス ハンドルを指定します。もし、dwThreadIdが現在のプロセスによって 作成されたスレッドを指していてかつ、 フックプロシージャが現在のプロセスに関連付けられたコード内にある 時はNULLにします。

dwThreadIdはフックプロシージャが関連付けられているスレッドのID を指定します。もし0ならすべてのスレッドがフックされます。

戻り値はフックプロシージャハンドルになります。失敗するとNULL が返されます。

BOOL UnhookWindowsHookEx( HHOOK hhk // 取り除くフックプロシージャハンドル );

hhkにはSetWindowsHookEx関数で返されたハンドルを指定します。

さて、フックプロシージャの書式は

LRESULT CALLBACK KeyProc(int nCode, WPARAM wParam, LPARAM lParam);

となります。ここで処理をすることになります。処理をしてそれで終わり の場合は0を返します。また処理後(もし存在すれば)次のフックプロシージャに情報を 渡すにはCallNextHookEx関数の戻り値を返します。

LRESULT CallNextHookEx( HHOOK hhk, // 現在のフックハンドル int nCode, // 現在のフックプロシージャに渡されたnCode WPARAM wParam, // 現在のフックプロシージャに渡されたwParam LPARAM lParam // 同じくlParam );

では、プログラムを見てみましょう。これは、親ウィンドウの クライアント領域にエディットコントロールを貼りつけたものです。 メニューの「フック」「開始」を選択するとキーボード フックが開始されます。このプログラムのすべてのキーボード操作 メッセージがフックされます。その結果キーボードの上のほうにある (テンキーではないという意味)数字の0-9以外は入力ができなくなります。

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

ごく普通のメニューです。

// hook00.cpp #ifndef STRICT #define STRICT #endif #define ID_EDIT 100 #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyHookProc(int, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void MyHookStart(HWND); void MyHookEnd(HWND); char szClassName[] = "hook00"; //ウィンドウクラス HHOOK hMyHook; 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 BOOL bHook = FALSE; HMENU hMenu; static HWND hEdit; RECT rc; HINSTANCE hInst; CREATESTRUCT *lpcs; switch (msg) { case WM_CREATE: lpcs = (CREATESTRUCT *)lp; hInst = lpcs->hInstance; GetClientRect(hWnd, &rc); hEdit = CreateWindow( "EDIT", NULL, WS_CHILD | WS_VISIBLE | ES_WANTRETURN | ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL | ES_AUTOHSCROLL | WS_HSCROLL, 0, 0, rc.right, rc.bottom, hWnd, (HMENU)ID_EDIT, hInst, NULL); break; case WM_SIZE: MoveWindow(hEdit, 0, 0, LOWORD(lp), HIWORD(lp), TRUE); break; case WM_INITMENU: hMenu = (HMENU)wp; if (bHook) { EnableMenuItem(hMenu, IDM_START, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_STOP, MF_BYCOMMAND | MF_ENABLED); } else { EnableMenuItem(hMenu, IDM_STOP, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hMenu, IDM_START, MF_BYCOMMAND | MF_ENABLED); } break; case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_START: MyHookStart(hWnd); bHook = TRUE; break; case IDM_STOP: MyHookEnd(hWnd); 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; }

WM_CREATEが来たらエディットコントロールを貼りつけます。

WM_SIZEが来たらエディットコントロールの大きさを調整します。

WM_INITMENUが来たらフック状態かどうかをbHookで調べて メニュー項目の選択可能、不可能状態を設定します。

WM_INITMENU hmenuInit = (HMENU) wParam; // 初期化されるメニューハンドル

これは、メニューがアクティブになろうとするときやってくるメッセージです。

void MyHookStart(HWND hWnd) { HINSTANCE hInst; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); hMyHook = SetWindowsHookEx(WH_KEYBOARD, //フック関数のタイプ (HOOKPROC)MyHookProc, //フックプロシージャのアドレス hInst, //フックプロシージャが入っているインスタンスハンドル 0); //フックされるスレッド 0ならすべてのスレッド if (hMyHook == NULL) MessageBox(hWnd, "フックに失敗しました", "Error", MB_OK); return; }

フックを開始させる関数です。

void MyHookEnd(HWND hWnd) { if (UnhookWindowsHookEx(hMyHook) != 0) MessageBox(hWnd, "フックを削除しました", "OK", MB_OK); else MessageBox(hWnd, "フック解除に失敗!", "Error", MB_OK); return; }

フックを削除する関数です。

LRESULT CALLBACK MyHookProc(int nCode, WPARAM wp, LPARAM lp) { if (nCode < 0) return CallNextHookEx(hMyHook, nCode, wp, lp); if (wp >= 0x30 && wp <=0x39) return CallNextHookEx(hMyHook, nCode, wp, lp); MessageBeep(MB_OK); return TRUE; }

フックプロシージャです。nCode, wp, lpの意味はフックのタイプにより異なります。 今回はWH_KEYBOARDなので

nCodeがHC_ACTIONならwpとlpに情報が入っています。

nCodeがHC_NOREMOVEならメッセージがメッセージキューから削除されていません。 もしnCodeがマイナスならば直ちにCallNextHookExを呼び出してその戻り値を返さなくては いけません。

wpには仮想キーコードが入ります。キーボードの「0」は0x30で「9」は0x39です。

lpには繰り返し数、スキャンコードなどが入ります。これはWM_KEYDOWNのlParamと 同じです。

さて、今回のプログラムでは自分のプログラム内だけでしかフックがおきません。 つまり、hook00.exeを起動してフックしても、その影響は「メモ帳」とか 「ワード」とか「一太郎」には及びません。

WH_KEYBOARDタイプのフックのスコープはスレッドとシステム全体と なっています。では、なぜ他のプログラムはフックできないのでしょうか。 これは、フックプロシージャをDLLにしていないからです。 Windows上のすべてのプログラム(プロセス)に対してフックができれば いろいろ面白いプログラムが作れそうですね。


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

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