2005年10月03日

グローバルフック・ザ・マウス

今更ながら、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 にしてやれば、そのときのマウスの動作をキャンセルできます。が、注意深く扱ってください。特にマウスは影響範囲が大きいですからね。

posted by Hongliang at 22:22| Comment(13) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
どうもこんにちは。
Googleが飛んできましたが、便利なコードありがたく使わせていただいてます。

ところで改善版コードをコンポーネントに追加しようとすると、

コンポーネント"Mouse hook"を追加できませんでした。
場所:MouseHook2.cs 行162

と表示されてしまいます。
ちょっと自分の腕では直しようがなかったので、
訂正お願いしたいです。

よろしくお願いします。
Posted by SIREN at 2006年11月10日 01:08
ども。
ほかにもそう言う方がいらっしゃるようなんですが、何せ私のところでは再現しませんで。

取りあえず、MouseHook クラスのデフォルト(引数無し)コンストラクタで
IntPtr module = Marshal.GetHINSTANCE(typeof(MouseHook).Assembly.GetModules()[0]);
としているところを
IntPtr module = Marshal.GetHINSTANCE(System.Reflection.Assembly.GetEntryAssembly().GetModules()[0]);
に書き換えてみて下さい。

// しかし今見ると IContainer を引数に取るコンストラクタ書いてなかったりするな……。
Posted by Hongliang at 2006年11月12日 04:54
初めまして。
グローバルフックについて調べていたところこちらを見つけました。

改善版コードをコンポーネントに追加しようとすると、SIRENさんと同様のエラーが出たので、コンストラクタの部分を書き換えたのですが、以下のようなエラーが発生してしまいます。

---------------------------
コンポーネント 'MouseHook' を生成できませんでした。エラー メッセージ:

'System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。

場所 HongliangSoft.Utilities.Gui.MouseHook..ctor()
---------------------------

私ではどこに問題があるのか分かりません。
ご助力お願いします。
Posted by REN at 2006年12月17日 22:21
どうも、VC#2005 なぞ半年に一度くらししか立ち上げない勢いの Hongliang です。
よく考えれば、確かに 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);
}
Posted by Hongliang at 2006年12月18日 01:32
ありがとうございます。
無事コンポーネントを追加することが出来ました。
Posted by REN at 2006年12月18日 23:28
はじめまして。
C#でマウスのグローバルフックができるということで、早速使わせてもらいました。
ありがとうございます。
しかし、1点問題が発生しており、対処法が見つからないため、ご相談させてください。

MouseHookでマウスのダブルクリックを取得したいと思い、enum MouseMessageに下記を追加したのですが、取得できませんでした。
LDblClk = 0x203,
RDblClk = 0x206,

VC++の場合はダブルクリックを取得する場合、CS_DBLCLKSを設定するようなのですが、C#ではどのように設定すればグローバルフックでダブルクリックを取得できるのでしょうか。

Posted by ASU at 2007年04月17日 20:03
記事の通り SetWindowsHookEx を利用しているわけですが、WH_MOUSE_LL をフックする際に使用するコールバック関数は LowLevelMouseProc と言う名前で MSDN に記述されています。
で、ここの記述の WPARAM の解説には以下の文言が含まれています。
「マウスメッセージの識別子を指定します。このパラメータで、WM_LBUTTONDOWN、WM_LBUTTONUP、WM_MOUSEMOVE、WM_MOUSEWHEEL、WM_RBUTTONDOWN、WM_RBUTTONUP のいずれかのメッセージを指定します」(訳が変ですけど)
と言うことで仕様っぽいです。
// その割に、MSLLHOOKSTRUCT 構造体の mouseData メンバの解説に「If the message is WM_XBUTTONDOWN, ...」などと書かれてたりしますが。

どうしてもというのなら、LDown が発生した位置と時間(とその位置のウィンドウ)を覚えておき、次に LDown が発生した時にダブルクリック内の範囲かどうか確認する、と言う手順が必要でしょうか。
Posted by Hongliang at 2007年04月17日 23:09
ご回答ありがとうございます。
WH_MOUSEとWH_MOUSE_LLの違いに気付いておりませんでした。
「グローバルフックでダブルクリックを取得」と書いてしまったのですが、やりたかったことは、対象のコントロール上のダブルクリックメッセージの制御だったので、ローカルフック(WH_MOUSE)で対応出来ました。

Window外へのマウスドラッグ処理も行いたかったので、ローカルフックとグローバルフックの両方を使うようにしました。
Posted by ASU at 2007年04月18日 19:33
はじめまして。
再現テストの操作ログ取得の為に使わしていただいております。

そこで気になる動作がひとつだけあります。
×ボタンをクリックすると5〜10秒ほど無反応の状態が続きそれから終了します。
その間はマウスも動きません。
Alt+F4ではこのような現象は起こりません。
Windows 2003 ServerやWindows 2000 Terminar Serverではこのような現象は起こりませんでした。

どのように対処すればいいでしょうか?
ご教授お願いいたします。

XP(SP3)
.NET2.0
VS2008(C#)
Posted by yachi at 2009年12月28日 15:35
申し訳ないですが、XP が手元にないし Vista では再現しないので、適切なコメントできませんね。
取り敢えず、まっさらなフォームにこれだけを貼り付けて動作確認するところから始めてはいかがでしょう。それから Dispose 内で時間を計測するとか。

// Component なのにコンストラクタで IContainer を引数に取らないとか、改めて見返すと酷い実装だなぁ。
Posted by Hongliang at 2010年01月08日 15:03
お返事ありがとうございます。

まっさらなフォームでテストしてみます。

ありがとうございました。
Posted by yachi at 2010年01月25日 14:34
通りすがりですが、すごく勉強になりました。
ありがとうございます。

海外でも同じようにマウスのクリックイベント等をコントロールの外、フォームの外でも検出する為の
グローバルフックのコード(VB.NET版)を見つけたのでリンクして置きますね。
http://vbcity.com/forums/p/120595/697749.aspx

Posted by 会社員 at 2011年06月10日 10:12
すごく勉強になりました。
ところで、stackoverflowによるとこの種のコードは、.Net Framework 4.0以降では、動作せず、
下記の元の箇所を新の様に書き換える必要があるみたいです。

元)
IntPtr module = Marshal.GetHINSTANCE(typeof(MouseHook).Assembly.GetModules()[0]);

新)
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr LoadLibrary(string lpFileName);
IntPtr module = LoadLibrary("user32.dll");

参照元)
http://stackoverflow.com/questions/17897646/gma-useractivitymonitor-setwindowshookex-error-126
Posted by クロノス at 2017年02月07日 01:20
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

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


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/7651626

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

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

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

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

×

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