2005年09月30日

グローバルフック・ザ・キーボード

今日は、過去に書いたコードの中から、キーボードフックを実装するクラスを紹介しましょう。

.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 から派生させ、キーボードのイベントをキャンセルできるようにしました。

posted by Hongliang at 23:54| Comment(13) | TrackBack(2) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
こんにちは。初めまして。

今、「KeyPlayer」というキーボードショートカットで操作するテープ起こし用メディアプレイヤーを作っています。
このエントリで公開してるクラスを組み込んで、アクティブじゃないウィンドウからもキーボード操作できるようにしたいのですが、作ったソフトやソースコードを公開して良いでしょうか?

こんな奴です。
http://web.sfc.keio.ac.jp/~t03792sh/archives/technology/programmingelectronics/keyplayer/

公開先は今のところ私のサイトと、Vectorだけです。
あと今後シェアウェア化する予定はありません。
Posted by shokai at 2006年03月19日 07:27
どうぞご自由にお使いください。
改変等も自在になさって構いません。再頒布等もご随意に。
ここで公開しているソースコードの使用に対し、私が何かを要求することはありません。
ただし、公開しているソースコードの使用によって何らかの損害等が発生した場合でも、私はいかなる責任も負わないこととします。

と言うことを、一度エントリに書いとかないと駄目ですな。

しかし改めて見直すとちょっと問題があるコードに仕上がってました。
・ファイナライザはつけちゃ駄目ですね。削除してください。
・hookDelegate フィールドは、このままでは GC によって別アドレスに移動する可能性があります。そうなった場合、Win32 はアクセスできなくなります。
 これを回避するため、コンストラクタにおいて、SetWindowsHookEx をする前に GCHandle.Alloc で GC から保護する必要があります。また Disposeで GCHandle.Free しなければなりません。
 それにともない、disposed フィールドを廃止して GCHandle.IsAllocated で Dispose 済みかどうかを判断することができるようになります。
 GCHandle については http://hongliang.seesaa.net/article/14551716.html も参照。
以上の変更を加えたのがこちらです。
http://hongliang.up.seesaa.net/sources/KeyboardHook2.cs

そう言う用途の場合、RegisterHotKey の方が向いてるかも知れません。
フックではアクティブなウィンドウにもメッセージが届くので。(それで|それが)いいものなら良いんですけど。
RegisterHotKey なら Win キーを修飾キーに使えたりと、まさにランチャ向けの仕様です。
次はこれで行くか。
Posted by Hongliang at 2006年03月19日 13:40
素早いバージョンアップと返事、ありがとうございます。さっそく組み込んで公開してみました。
RegisterHotKeyは気づいてませんでした。完成してから調べたら
http://homepage3.nifty.com/midori_no_bike/CS/userIO.html#264
どうやらこれでもできますね。なるほど。
Posted by shokai at 2006年03月19日 22:21
はじめまして。

Microsoftの公式で.NET FrameWorkではグローバルフックは対応していないと書かれていて諦めかけていた所、グーグルの検索でこちらに辿り着きました。

VBを長い事使っていることもあり、C#へ移れないでいるためVB2005でのサンプルコードも載せていただけないかと思い、書き込みさせていただきました。

よろしくお願いします。
Posted by saver at 2007年01月16日 19:11
申し訳ありませんが、やるつもりはありません。
逆にこの機会に C# のコードを読んでみてはいかがでしょうか? 実際に読んでみると、VB とはちょっとした文法、キーワードが違う程度の差しかない事に気付くはずです。C# 特有である unsafe のコードは私は出来る限り書いてないはずですし。
世のサンプルも読めるのが増えますよ。
C# to VB.NET Translator みたいなのもいくつかありますので、まずそう言うのを使って比較してみるとやりやすいかも知れません。

まあ、この記事のこれは VC# に突っ込んだらそれとなくクラスライブラリにすることはできるはずですけど。
Posted by Hongliang at 2007年01月17日 22:44
返信ありがとうございます。

書き込んだ後にVC#も少しやってみようかと思いまして、キーフックの動作を見ようとコードを書いてみたのですが、全くキーフックされませんでした。

KeyPlayer作者のShokiさんの所でサンプルコードを頂いて、みようみまねでコードを書いてみたのですが、ダメでした。
ShokiさんのソリューションをVC# 2005に読み込んでビルドし、exeファイルを直接実行するとキーフックをしてくれるので、自分が書いたコードが悪いか何かの設定を忘れているのだとは思うのですが、分からないままです。

もう少し頑張ってみたいと思います。
Posted by saver at 2007年01月18日 00:24
何が原因だったのかさっぱり分からないのですが、キーフックがされるようになりました。
コードを何も書き換えていないのですが、何がいけないのか調べてみようと本日実行してみたらキーフックされて、何がなんだか分かりませんでした(汗)

これを足掛かりとしてVBでやっていたことをVC#ではどう書くのかを勉強して行こうと思います。
Posted by saver at 2007年01月23日 00:22
大分古い記事だけどこのコードは最新環境でも動くもの?
自分が試した限りでは
.NET 3.5でXP SP2でフック出来なかった。
KeyPlayerも同環境でフックが機能しない。
Posted by kkkkkk at 2008年02月03日 19:05
結局以下のサイトのコードを使いましたが、
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からのデバッグ実行で出来なかったので、
こちらのサイトのコードでも動作したのかもしれません。

既に目的は達せたので改めて確かめはしませんが。
Posted by kkkkkk at 2008年02月03日 19:33
デバッガ上で動かすとたぶん失敗します。
SetWindowsHookEx を呼び出すときに自分自身のアセンブリのモジュールハンドルを使ってますが、デバッガだと指定すべきモジュールが変わるので。
Win32API の GetModuleHandle とか使うようにすれば回避できますが。
Posted by Hongliang at 2008年02月05日 22:04
こんにちわ。

グローバルフックについて調べていたら辿り着きました。
フック自体はできたのですが
元々やりたい事は自作アプリではなく別アプリの動作を制御する事で
自作アプリは時計みたいなもので、それで設定時刻になると
既存の別アプリに特定のキー入力を飛ばし、自動操作するという物です。
何故かsendinputとかを受け付けてくれないのでグローバルフックでやろうと思いました。
ですのでキー入力を監視して、そのキー入力を置き換えて別のキーを送信するというものではなく
入力がなくても任意のタイミングで一方的に送信したいのですが
そういった関数が見あたらないので、これは他に何か自分で組み込む必要があるんでしょうか?




Posted by RAW at 2010年12月14日 16:34
フックはフックの語が指すとおり、なんらかの出来事に引っかけて(フックして)コールバックを呼ばせる機能です。引っかける出来事がないのであればそれはフック以外の機能を使うことになります。
Windows ではユーザからの入力の取得方法を完全に一元化してはいないので、入力をエミュレートするにも、そのアプリケーションがどういう方法でキーの状態を取得しているか次第ですね。
Posted by Hongliang at 2010年12月16日 14:14
はじめまして、
今回GitHubに公開しているコードに"KeyboardHook3.cs"を含み公開したのでご報告させて頂きました。
https://github.com/homebox25136/Function-Key-Setting/

> あ、こんなのに使ったってコメントを頂ければ嬉しいです。
Posted by homebox25136 at 2015年08月12日 10:00
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック

KeyPlayer ver.1.10
Excerpt: 2ヶ月ぶりに「テープ起こし専用メディアプレイヤー」更新しました。 Vectorの...
Weblog: s.h.log
Tracked: 2006-03-19 22:08

正しいピアノ奏法―美しい音と優れたテクニ
Excerpt: ピアノを弾くときの手の動きを学術的に理解し、それに沿って効果的な、かつ最小の努力で出来るトレーニング法を紹介しています。実績もかなり上げている方のようなので、書かれている内容についても、信頼しても良さ..
Weblog: メソッドを...
Tracked: 2007-10-06 03:34

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。