总所周知:C#是.NET Framework平台的相伴语言,用它本身的类库和编译器提供的方法是无法实现全局钩子的。但实际上对于非托管代码的调用在C#中是成立的,使用DllImport属性可以引用非托管代码类库中的方法。钩子函数存在于user32.dll中,函数原型如下:
HHOOK WINAPI SetWindowsHookEx(
__in int idHook,
__in HOOKPROC lpfn,
__in HINSTANCE hMod,
__in DWORD dwThreadId);
使用它可以向操作系统(Windows)注册一个特定类型的消息拦截处理方法,例如我们可以注册一个拦截全局键盘消息的钩子,那么所有的键盘按下、抬起事件都可以被我们感知和处理(不排除有前端钩子将消息丢弃的情况)。
我们在C#中可以如下声明来引用这个函数:
[DllImport("user32.dll")]
public static extern int SetWindowsHookEx(
HookType idHook,
HookProc lpfn,
IntPtr hInstance,
int threadId
);
值得一提的是上面的HookType和HookProc是我自定义的类型,这无关紧要(因为程序运行时传递的是内存地址嘛),但必须符合一定规范。
函数的参数从上到下依次为:
idHook钩子类型,此处用整形的枚举表示
lpfn钩子发挥作用时的回调函数
hInstance应用程序实例的模块句柄(一般来说是你钩子回调函数所在的应用程序实例模块句柄)
threadId与安装的钩子子程相关联的线程的标识符
钩子的类型有以下几种:
我们一般会使用13拦截键盘消息,14拦截鼠标消息。
回调函数的声明我们在C#里需要用到委托,声明如下:
public delegate int HookProc(int nCode, int wParam, IntPtr lParam);
从上而下参数意义为:nCode钩子链传递回来的参数,0表示此消息(被之前的消息钩子)丢弃,非0表示此消息继续有效
wParam消息参数
lParam消息参数
值得一提的是wParam和lParam在不同的消息类型中是不一样的类型,不过wParam的类型大概可以用下面的枚举表示:
而lParam一般被封装为结构,因消息类型而异,如下的两个结构分别是鼠标和键盘消息的lParam结构:
当我们了解了以上信息时,我们就基本了解了钩子函数的C#实现了,然后注意几个问题就好:
1.钩子对资源占用很多,不用时应及时取消掉,这个需要使用UnhookWindowsHookEx函数
2.处于礼貌,钩子应返回下一个钩子的处理结果,而不是单一地将当前钩子的处理结果返回(使用CallNextHookEx调用下一个钩子,由于钩子是先设置后生效,所以应该如此来保证钩子链的正常传递)
3.钩子函数参数中的hInstance是只当前钩子的回调函数在哪儿,一定要给出正确地址
4.因为使用了委托,应该保证委托的内存地址(对方法的引用)不会垃圾回收,否则在钩子执行时会出现异常
大家可以下载我写的示例程序,不过我的程序有以下的地方需要主要:
1.我将钩子的实现屏蔽了,只对外开放了键盘和鼠标的消息拦截和处理(使用方式和C#的WinForm鼠标键盘事件一致),你可以开放出来其它的
2.我将钩子设计为单例模式,你可以取消
3.由于对操作系统有一定侵入,杀毒软件可能会报出有风险
4.代码写得很糟糕,凑合着看吧……
最后说一句:编程技术和语言的关系不大,语言的区别在于它们的编译器和他们的使用者,使用C的人不能说一定比使用Java的高等,而技术也不一定体现在指针、矩阵、数据结构上,只要对计算机原理、编译原理、操作系统原理等了解的人都明白。当然,大家有自己的喜欢的语言和惯用的编程手法固然是很好的。
|