Vistaでフックを使うときは注意が必要

標準的なキーボードフックについては以前打鍵のtomoでも実装したので,それほど問題なく作れるだろう思っていたのに結局一晩まるまる使ってしまった.

その原因がVistaのユーザ権限管理..
キーボードフックの登録をDLL側で行うと,Windows上で発生するすべてのアプリケーションへのキー入力(WM_KEYDOWN,WM_KEYUP)を取得することができるが,この打鍵に対して処理を行うにはDLL側からプログラム本体に通知してやる必要がある.

           PostMessage
本体.exe       ←       フック.DLL
                            ↑
各種Winアプリ  →     キー入力メッセージ

このときに使われるのがPostMessageというやつで,その名の通り別のプログラムに何か(ここではキーのUP・DOWN情報)を通知するために使う関数だ.

Vista以前のWindowsではこのメッセージ通知には何の制限も無かったようなのだが,Vistaからはプログラムごとの権限が明確に決められるようになって,より上位の権限を持つプログラムに対してメッセージを通知することができなくなったようだ.PostMessageはエラーを返し,GetLastErrorではアクセス関係の[5]を返す.

これ自体はわかりやすい話で,階級がずっと下の一兵卒が国王とかに対して話しかけることができないという感じ.ちなみに今回の場合は管理者権限で起動したVisualStudioから本体.exeを起動したときにこの問題が起きていることに気づいたので,イメージ的には国王から権限を譲渡された将軍に対してもやっぱり話しかけることが許されないって感じだろうか.

本体.exeを管理者権限で実行すると,フックの機構上PostMessageは各アプリから通知されることになる.各アプリは通常権限で起動しているので,結局PostMessageを受けることができない.すなわち,キー入力が起きたかどうかを知ることができない.

dvorakkrと姫踊子草で確認してみたところ,姫踊子草でのみこの問題が起きることを確認できた.つまり,管理者権限で起動するとまったくキーの入れ換え機能が動作しない.dvorakkrで問題が発生しない理由がよくわからないけど,これは別のフックを使っているか,メッセージ伝達方法を使っているのかもしれない...

んで,大事な解決方法.まず参考ページ.Vistaの互換性ページの存在は知っていたけど,こんなところで実際に使うことになるとは思わなかった.
ユーザー インターフェイス特権の分離 (UIPI)

上のページでは概要しか載っていないので,コードも載せておく.

#include <windows.h>

typedef BOOL (__stdcall *FUNCTYPE)(UINT, DWORD);
#define MSGFLT_ADD 1
#define MSGFLT_REMOVE 2

void main(){
  FUNCTYPE ChangeWindowMessageFilter;
  HMODULE dll = LoadLibrary(TEXT("user32.dll"));
  ChangeWindowMessageFilter =
       (FUNCTYPE)GetProcAddress(LoadLibrary(TEXT("user32.dll")) ,
                               "ChangeWindowMessageFilter");
  // Vista以外のOSではこの関数は存在しないが,
  // そもそもメッセージのフィルタリングとかされないので問題なし
  if(ChangeWindowMessageFilter != NULL){
    ChangeWindowMessageFilter(WM_KEYDOWN, MSGFLT_ADD);
    ChangeWindowMessageFilter(WM_KEYUP, MSGFLT_ADD);
  }
  FreeLibrary(dll);
}

ChangeWindowMessageFilterというのが他のアプリからメッセージを受け取れるように設定できる関数で,ここでは受け取りたいメッセージであるWM_KEYDOWNとWM_KEYUPを指定している.

includeだけすりゃすぐに使えるようになるかと思ったが,VisualStudio2005を入れただけのヘッダーファイルにはChangeWindowMessageFilterが定義されていなかったため使えない.

おそらく,Windows Platform SDKを入れなければいけないんだろうが,この関数のためだけに1GB近く落とすのも面倒なので,LoadLibraryで使うことにした.関数がなければVistaではないので問題ないはず.

※管理者権限で起動しなければ問題ないんじゃね?
と思う人もいるだろうが,キー入力監視を行うプログラムを管理者権限で起動しないと,他の管理者権限で起動しているプログラムに対するキー入力メッセージを取得することができないのだ.

Vistaややこしす.