在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全局钩子也是一样的问题,把处理钩子回调的部分放置到单独的线程消息循环中应该能解决问题,这里就不做测试。