グローバルキーフック(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メッセージを横取りするイメージ図。いろいろと間違ってるかも。

20140729_keyhook.png

2つの仮想アドレス空間にマッピングされたkeyhook.dllは、共通のグローバル変数(HHOOK型)を持つ必要がある。でなければフックプロシージャをアンインストールする時に困る。
フックプロシージャをインストールするプロセスと、アンインストールするプロセスが同じ「hoge.exe」であれば必要ないかもしれないけど、「notepad.exe」のような別のプロセスからアンインストールする実装にしたい場合は必要となる。

C++では#pragma data seg(“”)で共有セグメント(領域)を確保できる。
これを確保しないとグローバル変数(HHOOK型)は各プロセス間で共通の値を代入することができない。(Windowsはプロセスのアドレス空間が他のプロセスから見られないように分割管理している。)

今回の目的はグローバルホットキーの役目を果たしてもらうことなので、フックプロシージャは目的のウインドウ(↑の図で言うhoge.exe)にキーストロークメッセージと、キーコードを転送するだけのもの。
キーコード転送先のウインドウハンドルを、dllをマッピングした全プロセスで共有しなければならないので、ここでも共有セグメントが必要。
しかしながら無理にグローバルフックを使うのは行儀の悪いことなので、必要がない限りはRegisterHotKey関数にしといたほうがよさそう。

DLL等のダウンロード

globalKeyHook.zip (7 KB)   923 ダウンロード

仕様など

フックはするが、メッセージを転送するだけ。
メッセージを転送するのは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のソースコード

Leave a Reply

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です