在windows中可以使用WH_MOUSE和WH_MOUSE_LL兩種鈎子來監控鼠標消息,兩者的使用是有區別的,前者注冊的鈎子回調函數是在應用程序調用GetMessage或者PeekMessage時且應用程序的消息隊列中存在鼠標消息時,鈎子回調函數先於GetMessage、PeekMessage被調用;后者則是鼠標事件在即將被置入到線程的輸入隊列時被調用,這些鼠標事件可以是從鼠標驅動程序產生,還可以是來自於mouse_event函數。兩者都可以是全局的鈎子,即不只監控應用程序的鼠標消息,還可以監控其他應用程序線程的鼠標消息,這體現在使用SetWindowHookEx()注冊鈎子dwThreadId參數是否指定某個特定的線程或者為0表示所有線程,但WH_MOUSE的回調函數必須是在DLL中,在調用時注入到其他進程;而WH_MOUSE_LL不必是這樣,系統在調用它的回調函數前會做進程切換,總是在切換到注冊鈎子的進程后再調用回調函數,所以WH_MOUSE_LL的回調函數不一定非要在DLL中實現並導出。
回到C#中對鼠標鈎子函數的實現,在MSDN上有一個例子:http://support.microsoft.com/kb/318804。程序使用WH_MOUSE實現了一個局部的鼠標鈎子,我們要注意的是最后一段話:“
在 .NET 框架中不支持全局掛鈎
您無法在 Microsoft .NET 框架中實現全局掛鈎。若要安裝全局掛鈎,掛鈎必須有一個本機動態鏈接庫 (DLL) 導出以便將其本身插入到另一個需要調入一個有效而且一致的函數的進程中。這需要一個 DLL 導出,而 .NET 框架不支持這一點。托管代碼沒有讓函數指針具有統一的值這一概念,因為這些函數是動態構建的代理。”
由於C#的DLL函數是運行時動態編譯創建的,因此你無法在一個托管的DLL中導出一個回調函數,因而托管代碼是無法使用WH_MOUSE實現全局鈎子的,即使你的回調函數中是在一個C#實現的DLL中,你會發現SetWindowHookEx()可以成功注冊鈎子,但是到了調用回調函數以及卸載鈎子的時候都會出錯,那個時候回調函數可能被優化到別的地址甚至不存在了。但是如前所言,在C#使用WM_MOUSE_LL實現全局鈎子是沒有問題的,可以找到很多它的例子(http://www.codeproject.com/Articles/7294/Processing-Global-Mouse-and-Keyboard-Hooks-in-C)。
還需要說明的是使用全局WM_MOUSE_LL鼠標鈎子會讓某些操作變得很慢,比如最大最小化或者關閉注冊全局鈎子的程序時,會感覺到明顯的停頓,這種現象在關閉系統的動畫效果后會得到很大的改善,原因可能和鈎子回調函數的時機順序有關,在VC++中使用WM_MOUSE_LL全局鈎子也是一樣的問題,把處理鈎子回調的部分放置到單獨的線程消息循環中應該能解決問題,這里就不做測試。