今日は、過去に書いたコードの中から、キーボードフックを実装するクラスを紹介しましょう。
.NETではフックに関して、「グローバルフックは使えない」と説明されています。DLLにグローバルフックで使用されるコールバック関数を実装しなければならないのに、.NETではそのDLLを作れないからです。
なんですが、何故かWH_KEYBOARD_LLやWH_MOUSE_LLはグローバルフックが可能なようなのです。理由は定かではありませんが。ひょっとしたら何らかの環境依存があるのかも知れません。ですから当然動作保証などできません。少なくとも私の環境では動いています。
コードそのものはそう大したことはしていません。SetWindowsHookExとその周辺を軽くラップした程度です。
テクニックとしては、直接SetWindowsHookExが要求するコールバックを外に見せるのではなく、コールバックメソッドそのものは内部で持って、その内部のコールバックメソッドからユーザが登録するイベントとして別のより認識しやすい形にしたデリゲート型のイベントを定義している事でしょうか。ああ、言葉で言うと本当に分かりづらいですね。こういう形にしたのには当然訳があって、一旦アンマネージドにデリゲートの参照を渡してしまうと、その後そのデリゲートに新たにハンドラを加えても(+=しても)、アンマネージド側はその後から加わったハンドラは認識も実行もできないんですね。それで、アンマネージドに渡すハンドラはクラスのプライベートメソッドに限定して、ユーザが使用するイベントにはもっとかみ砕いたイベントハンドラを用意しようと言うわけです。これならハンドラの追加も削除も自由自在。
注意点として、簡単なクラスなので何かのメソッド内で宣言・設定をまとめてやってしまいがちですが、宣言をメソッド内でやってしまうと、そのメソッドが終了時にKeyboardHookオブジェクトへの参照が無くなってしまってGCの対象になってしまいます。複数インスタンスを使うクラスでもないですし、staticフィールドとして宣言しておくのが良いでしょう。またWindows 9x系では動きません。
今回のコードはC#のみです。記事に直に載せたりもしませんので、直接csファイルをどうぞ。
2006/03/19 追記。ここのコメント欄で書いた通り、KeyboardHook.cs の内容が多少古くなっていましたので、ちょっとだけ手直ししたバージョンを公開します。以前のものも残しておくので見比べてみてください。
2006/04/13 追記。もうちょっと機能拡充すべく わずかに手を加えました。今回の変更点は、まず DefaultEvent 属性を付けたこと。これでフォームに D&D したあと KeyboardHook コンポーネントをダブルクリックするだけでイベントが記述されます。自分が VS 使わないものだからこの辺気付きませんでした(笑)。そして機能の拡張として、KeyboardHookedEventArgs を CancelEventArgs から派生させ、キーボードのイベントをキャンセルできるようにしました。
今、「KeyPlayer」というキーボードショートカットで操作するテープ起こし用メディアプレイヤーを作っています。
このエントリで公開してるクラスを組み込んで、アクティブじゃないウィンドウからもキーボード操作できるようにしたいのですが、作ったソフトやソースコードを公開して良いでしょうか?
こんな奴です。
http://web.sfc.keio.ac.jp/~t03792sh/archives/technology/programmingelectronics/keyplayer/
公開先は今のところ私のサイトと、Vectorだけです。
あと今後シェアウェア化する予定はありません。
改変等も自在になさって構いません。再頒布等もご随意に。
ここで公開しているソースコードの使用に対し、私が何かを要求することはありません。
ただし、公開しているソースコードの使用によって何らかの損害等が発生した場合でも、私はいかなる責任も負わないこととします。
と言うことを、一度エントリに書いとかないと駄目ですな。
しかし改めて見直すとちょっと問題があるコードに仕上がってました。
・ファイナライザはつけちゃ駄目ですね。削除してください。
・hookDelegate フィールドは、このままでは GC によって別アドレスに移動する可能性があります。そうなった場合、Win32 はアクセスできなくなります。
これを回避するため、コンストラクタにおいて、SetWindowsHookEx をする前に GCHandle.Alloc で GC から保護する必要があります。また Disposeで GCHandle.Free しなければなりません。
それにともない、disposed フィールドを廃止して GCHandle.IsAllocated で Dispose 済みかどうかを判断することができるようになります。
GCHandle については http://hongliang.seesaa.net/article/14551716.html も参照。
以上の変更を加えたのがこちらです。
https://hongliang.up.seesaa.net/sources/KeyboardHook2.cs
そう言う用途の場合、RegisterHotKey の方が向いてるかも知れません。
フックではアクティブなウィンドウにもメッセージが届くので。(それで|それが)いいものなら良いんですけど。
RegisterHotKey なら Win キーを修飾キーに使えたりと、まさにランチャ向けの仕様です。
次はこれで行くか。
RegisterHotKeyは気づいてませんでした。完成してから調べたら
http://homepage3.nifty.com/midori_no_bike/CS/userIO.html#264
どうやらこれでもできますね。なるほど。
Microsoftの公式で.NET FrameWorkではグローバルフックは対応していないと書かれていて諦めかけていた所、グーグルの検索でこちらに辿り着きました。
VBを長い事使っていることもあり、C#へ移れないでいるためVB2005でのサンプルコードも載せていただけないかと思い、書き込みさせていただきました。
よろしくお願いします。
逆にこの機会に C# のコードを読んでみてはいかがでしょうか? 実際に読んでみると、VB とはちょっとした文法、キーワードが違う程度の差しかない事に気付くはずです。C# 特有である unsafe のコードは私は出来る限り書いてないはずですし。
世のサンプルも読めるのが増えますよ。
C# to VB.NET Translator みたいなのもいくつかありますので、まずそう言うのを使って比較してみるとやりやすいかも知れません。
まあ、この記事のこれは VC# に突っ込んだらそれとなくクラスライブラリにすることはできるはずですけど。
書き込んだ後にVC#も少しやってみようかと思いまして、キーフックの動作を見ようとコードを書いてみたのですが、全くキーフックされませんでした。
KeyPlayer作者のShokiさんの所でサンプルコードを頂いて、みようみまねでコードを書いてみたのですが、ダメでした。
ShokiさんのソリューションをVC# 2005に読み込んでビルドし、exeファイルを直接実行するとキーフックをしてくれるので、自分が書いたコードが悪いか何かの設定を忘れているのだとは思うのですが、分からないままです。
もう少し頑張ってみたいと思います。
コードを何も書き換えていないのですが、何がいけないのか調べてみようと本日実行してみたらキーフックされて、何がなんだか分かりませんでした(汗)
これを足掛かりとしてVBでやっていたことをVC#ではどう書くのかを勉強して行こうと思います。
自分が試した限りでは
.NET 3.5でXP SP2でフック出来なかった。
KeyPlayerも同環境でフックが機能しない。
http://azumaya.s101.xrea.com/wiki/index.php?%B3%D0%BD%F1%2FC%A2%F4%2F%A5%B0%A5%ED%A1%BC%A5%D0%A5%EB%A5%D5%A5%C3%A5%AF
これも最初はフックできなかったのですが、
このページの冒頭に書かれているように
VSのデバッグオプションを設定したところキーフックできました。
KeyPlayerのフックが出来ないと書いたのもVSからのデバッグ実行で出来なかったので、
こちらのサイトのコードでも動作したのかもしれません。
既に目的は達せたので改めて確かめはしませんが。
SetWindowsHookEx を呼び出すときに自分自身のアセンブリのモジュールハンドルを使ってますが、デバッガだと指定すべきモジュールが変わるので。
Win32API の GetModuleHandle とか使うようにすれば回避できますが。
グローバルフックについて調べていたら辿り着きました。
フック自体はできたのですが
元々やりたい事は自作アプリではなく別アプリの動作を制御する事で
自作アプリは時計みたいなもので、それで設定時刻になると
既存の別アプリに特定のキー入力を飛ばし、自動操作するという物です。
何故かsendinputとかを受け付けてくれないのでグローバルフックでやろうと思いました。
ですのでキー入力を監視して、そのキー入力を置き換えて別のキーを送信するというものではなく
入力がなくても任意のタイミングで一方的に送信したいのですが
そういった関数が見あたらないので、これは他に何か自分で組み込む必要があるんでしょうか?
Windows ではユーザからの入力の取得方法を完全に一元化してはいないので、入力をエミュレートするにも、そのアプリケーションがどういう方法でキーの状態を取得しているか次第ですね。
今回GitHubに公開しているコードに"KeyboardHook3.cs"を含み公開したのでご報告させて頂きました。
https://github.com/homebox25136/Function-Key-Setting/
> あ、こんなのに使ったってコメントを頂ければ嬉しいです。
こちらのサンプルプログラムを参考にさせていただいております。
うまくグローバルフックも検知できたのですが、キーボードの同時押しはどのように検知したらいいでしょうか。
UpDownを識別して押下中のキーを管理することでできました。
にて確認したのですが、
タスクマネージャー等の一部のウィンドウは
キーが取得できませんでした。
また、処理中に重い処理があるとキー入力が止まります。