グローバルキーフック(Win32 API C++)
色々とメモメモ
アプリケーションが非アクティブでも受け付けるホットキーを実装しようと思った時、手軽なのがWindowsAPIのRegisterHotKey()関数だけど、同一のキーを複数のアプリケーションで使用することはできない。
どんな状況でも確実にキー入力を監視したい、となったらグローバルフックを使うしか無い。
キー入力(WM_KEYDOWNメッセージ)を引っ掛けるには、SetWindowsHookEx関数でフックタイプにWH_KEYBOARDを指定してあげればいいみたい。しかしこの関数、関数を呼び出したプロセスに飛んだメッセージしかフックしてくれない。これではグローバルホットキーにならない。
システムグローバルなフックを実現するには、DLLでSetWindowsHookEx関数を呼び出し、フックプロシージャの処理もDLL内に記述する必要がある。このDLLはすべてのプロセスの仮想アドレス空間にマッピングされ、フックプロシージャ内の処理を行うことを可能にする。
以下は、keyhook.dllを動的リンクしたhoge.exeがdll内のSetWindowHookEx関数(フックタイプWH_KEYBOARD)を呼び出し、メモ帳(notepad.exe)に送られたWM_KEYDOWNメッセージを横取りするイメージ図。いろいろと間違ってるかも。
2つの仮想アドレス空間にマッピングされたkeyhook.dllは、共通のグローバル変数(HHOOK型)を持つ必要がある。でなければフックプロシージャをアンインストールする時に困る。
フックプロシージャをインストールするプロセスと、アンインストールするプロセスが同じ「hoge.exe」であれば必要ないかもしれないけど、「notepad.exe」のような別のプロセスからアンインストールする実装にしたい場合は必要となる。
C++では#pragma data seg(“”)で共有セグメント(領域)を確保できる。
これを確保しないとグローバル変数(HHOOK型)は各プロセス間で共通の値を代入することができない。(Windowsはプロセスのアドレス空間が他のプロセスから見られないように分割管理している。)
今回の目的はグローバルホットキーの役目を果たしてもらうことなので、フックプロシージャは目的のウインドウ(↑の図で言うhoge.exe)にキーストロークメッセージと、キーコードを転送するだけのもの。
キーコード転送先のウインドウハンドルを、dllをマッピングした全プロセスで共有しなければならないので、ここでも共有セグメントが必要。
しかしながら無理にグローバルフックを使うのは行儀の悪いことなので、必要がない限りはRegisterHotKey関数にしといたほうがよさそう。
DLL等のダウンロード
globalKeyHook.zip (7 KB) 902 ダウンロード仕様など
フックはするが、メッセージを転送するだけ。
メッセージを転送するのはKeyDown状態の時だけ。KeyUp状態は無視。
システムキー( Alt(+何か)もしくはF10 )について。Alt(+何か)については、WM_SYSKEYDOWNを転送するが、F10はWM_KEYDOWNで転送してしまうので注意。
使い方
使い方は、globalKeyHook.hをインクルードして、コード中の任意の場所で SetHook(HWND hWnd) を呼ぶだけ。
hWndに、キーストロークメッセージを転送するウインドウを指定する。
プロセスの終了前に、ResetHook() を絶対に忘れないようにする。
リンク時にglobalKeyHook.libをリンクする。
exe実行時に、globalKeyHook.dllを同じディレクトリに置いておく。
その他
フックプロシージャのlParamの値についてはこちらhttp://msdn.microsoft.com/ja-jp/library/cc430012.aspx
WH_KEYBOARD_LL のフックタイプであれば、DLL化は要らないらしい。(呼び出し元exe以外のプロセスにDLLがアタッチしないことを自分で確認)
WH_KEYBOARD_LL のやり方であれば、メッセージがwParamに入っているので、SYSKEYDOWNとKEYDOWNの区別が容易。F10キーも問題ないはず。
以下、DLLのソースコード
1 2 3 |
LRESULT CALLBACK KeyHookProc(int, WPARAM, LPARAM); int SetHook(HWND hWnd); int ResetHook(); |
1 2 3 4 5 6 7 8 9 |
LIBRARY globalKeyHook SECTIONS SHARED_SEG READ WRITE SHARED EXPORTS KeyHookProc @1 SetHook @2 ResetHook @3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#include <windows.h> #include "globalKeyHook.h" //共有セグメント #pragma data_seg("SHARED_SEG") HHOOK hKeyHook = 0; HWND g_hWnd = 0; //キーコードの送り先のウインドウハンドル #pragma data_seg() HINSTANCE hInst; bool bSetHook; //SetHook関数を呼んだプロセスであるか BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD dwReason, LPVOID lpReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: //アタッチ hInst = hInstDLL; bSetHook = FALSE; break; case DLL_PROCESS_DETACH: //デタッチ break; } return TRUE; } int SetHook(HWND hWnd) { hKeyHook = SetWindowsHookEx(WH_KEYBOARD, KeyHookProc, hInst, 0); if (hKeyHook == NULL){ //フック失敗 } else { bSetHook = TRUE; //フック成功 g_hWnd = hWnd; } return 0; } int ResetHook() { if (UnhookWindowsHookEx(hKeyHook) != 0){ //フック解除成功 } else{ //フック解除失敗 } return 0; } LRESULT CALLBACK KeyHookProc(int nCode, WPARAM wp, LPARAM lp) { if (nCode < 0) //決まり事 return CallNextHookEx(hKeyHook, nCode, wp, lp); if(nCode==HC_ACTION){ //目的のウインドウにキーボードメッセージと、キーコードの転送 //(SetHookを行ったウインドウで押されたボタンは除く) if(!bSetHook){ //ボタンが押された状態の時限定(離しはスルー) if( (lp & 0x80000000)==0){ //通常キー if( (lp & 0x20000000)==0) PostMessage(g_hWnd, WM_KEYDOWN, wp, 0); //システムキー(Alt(+何か)、もしくはF10の時) else PostMessage(g_hWnd, WM_SYSKEYDOWN, wp, 0); } } } return CallNextHookEx(hKeyHook, nCode, wp, lp); } |
最近のコメント