今更ながら、DDEML関数を.NETにマッピングしてみています。実は『DDEって何?』レベルからのスタートなんですが。一通り調べた感想は、まあ要するに規格化されたメッセージだなと。いやそのままですが。直接DDEメッセージを扱うのではなく、DDEML関数を通して扱うとなると実体が見えにくいので。しかし一番手間取るのがドキュメント部分だったり……。
さて今日は、前回のと対をなす、マウスフックを実装するクラスを紹介しましょう。
これもグローバルフックを使用しますが、なぜか.NETで可能なので。あ、でもNT系しか使えません。
コードを解説すると、基本的なところはというかほとんどKeyboardHookと同じです。ちょっとした技巧としてFieldOffset属性を使用して共有体もどきを作っているくらいでしょうか。
注意点として、キーボードフックと同じく簡単なクラスなので何かのメソッド内で宣言・設定をまとめてやってしまいがちですが、宣言をメソッド内でやってしまうと、そのメソッドが終了時にMouseHookオブジェクトへの参照が無くなってしまってGCの対象になってしまいます。複数インスタンスを使うクラスでもないですし、staticフィールドとして宣言しておくのが良いでしょう。繰り返しますがWindows 9x系では動きません。
Win32APIをマッピングする際に悩むのがどうコールバックをイベントにするか、というかイベントデータクラスをどう書くかです。私はできる限りIntPtrやビットを隠して、分かりやすいEventArgs派生クラスを作るように心がけていますが……面倒なことこの上ないです。
今回のコードもC#のみです。記事に直に載せたりもしませんので、直接csファイルをどうぞ。要望があればVB.NETにも移植しますけど。そう言えば昨日だったか、まさにぴたりと「VB.NET フック」でいらっしゃった方がいたようですが。
一応キーワードを播いておきましょう。C#による、SetWindowsHookExを使った、WH_MOUSE_LLの実装、が今回の主題でした。
06/04/13 追記。当ブログ二番目くらいに人気のグローバルフックですが、問い合わせがあった KeyboardHook の方はさっさと修正を加えたのですがこちらは放置しっぱなしでしたので、改めて 改善版のコード を出しておきましょう。また、今回新たに MouseHookedEventArgs を EventArgs からではなく CancelEventArgs から派生させました。イベントハンドラで Cancel プロパティを true にしてやれば、そのときのマウスの動作をキャンセルできます。が、注意深く扱ってください。特にマウスは影響範囲が大きいですからね。


Googleが飛んできましたが、便利なコードありがたく使わせていただいてます。
ところで改善版コードをコンポーネントに追加しようとすると、
コンポーネント"Mouse hook"を追加できませんでした。
場所:MouseHook2.cs 行162
と表示されてしまいます。
ちょっと自分の腕では直しようがなかったので、
訂正お願いしたいです。
よろしくお願いします。
ほかにもそう言う方がいらっしゃるようなんですが、何せ私のところでは再現しませんで。
取りあえず、MouseHook クラスのデフォルト(引数無し)コンストラクタで
IntPtr module = Marshal.GetHINSTANCE(typeof(MouseHook).Assembly.GetModules()[0]);
としているところを
IntPtr module = Marshal.GetHINSTANCE(System.Reflection.Assembly.GetEntryAssembly().GetModules()[0]);
に書き換えてみて下さい。
// しかし今見ると IContainer を引数に取るコンストラクタ書いてなかったりするな……。
グローバルフックについて調べていたところこちらを見つけました。
改善版コードをコンポーネントに追加しようとすると、SIRENさんと同様のエラーが出たので、コンストラクタの部分を書き換えたのですが、以下のようなエラーが発生してしまいます。
---------------------------
コンポーネント 'MouseHook' を生成できませんでした。エラー メッセージ:
'System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。
場所 HongliangSoft.Utilities.Gui.MouseHook..ctor()
---------------------------
私ではどこに問題があるのか分かりません。
ご助力お願いします。
よく考えれば、確かに VS のツールボックスから D&D する際に GetEntryAssembly してもアセンブリから起動されている保証がないんですよね。実際これが null を返すために失敗しています。
ですので、結局こういうコードになりそうです。
// ファイル先頭部分にて
using System.Diagnostics;
// KeyboardHook クラスのメソッドとして
[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern IntPtr GetModuleHandle(string moduleName);
// コンストラクタの module 取得部分〜 this.hook 設定部分
using (Process current = Process.GetCurrentProcess()) {
IntPtr module = GetModuleHandle(current.MainModule.ModuleName);
this.hook = SetWindowsHookEx(KeyboardHookType, callback, module, 0);
}
無事コンポーネントを追加することが出来ました。
C#でマウスのグローバルフックができるということで、早速使わせてもらいました。
ありがとうございます。
しかし、1点問題が発生しており、対処法が見つからないため、ご相談させてください。
MouseHookでマウスのダブルクリックを取得したいと思い、enum MouseMessageに下記を追加したのですが、取得できませんでした。
LDblClk = 0x203,
RDblClk = 0x206,
VC++の場合はダブルクリックを取得する場合、CS_DBLCLKSを設定するようなのですが、C#ではどのように設定すればグローバルフックでダブルクリックを取得できるのでしょうか。
で、ここの記述の WPARAM の解説には以下の文言が含まれています。
「マウスメッセージの識別子を指定します。このパラメータで、WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE、WM_MOUSEWHEEL、WM_RBUTTONDOWN、WM_RBUTTONUP のいずれかのメッセージを指定します」(訳が変ですけど)
と言うことで仕様っぽいです。
// その割に、MSLLHOOKSTRUCT 構造体の mouseData メンバの解説に「If the message is WM_XBUTTONDOWN, ...」などと書かれてたりしますが。
どうしてもというのなら、LDown が発生した位置と時間(とその位置のウィンドウ)を覚えておき、次に LDown が発生した時にダブルクリック内の範囲かどうか確認する、と言う手順が必要でしょうか。
WH_MOUSEとWH_MOUSE_LLの違いに気付いておりませんでした。
「グローバルフックでダブルクリックを取得」と書いてしまったのですが、やりたかったことは、対象のコントロール上のダブルクリックメッセージの制御だったので、ローカルフック(WH_MOUSE)で対応出来ました。
Window外へのマウスドラッグ処理も行いたかったので、ローカルフックとグローバルフックの両方を使うようにしました。
再現テストの操作ログ取得の為に使わしていただいております。
そこで気になる動作がひとつだけあります。
×ボタンをクリックすると5〜10秒ほど無反応の状態が続きそれから終了します。
その間はマウスも動きません。
Alt+F4ではこのような現象は起こりません。
Windows 2003 ServerやWindows 2000 Terminar Serverではこのような現象は起こりませんでした。
どのように対処すればいいでしょうか?
ご教授お願いいたします。
XP(SP3)
.NET2.0
VS2008(C#)
取り敢えず、まっさらなフォームにこれだけを貼り付けて動作確認するところから始めてはいかがでしょう。それから Dispose 内で時間を計測するとか。
// Component なのにコンストラクタで IContainer を引数に取らないとか、改めて見返すと酷い実装だなぁ。
まっさらなフォームでテストしてみます。
ありがとうございました。
ありがとうございます。
海外でも同じようにマウスのクリックイベント等をコントロールの外、フォームの外でも検出する為の
グローバルフックのコード(VB.NET版)を見つけたのでリンクして置きますね。
http://vbcity.com/forums/p/120595/697749.aspx