二. 背景
其实,已经存在许多有关于Windows钩子的文章和示例代码,并且已经有人编写过与本文几乎一样的C++示例程序。然而,当我搜索相应的C#应用程序的源码时,却找到极少的.NET示例,而且没有一个程序能够提供一个方便的自包含的C#类。
.NET框架能够使你以托管方式来存取你最常使用的键盘事件(通过KeyPress,KeyUp和KeyDown)。遗憾的是,这些事件都不能被用来停止Windows组合键(如Alt+Tab或Windows"开始"键),从而允许用户"远离"某一个应用程序。
本文的想法在操作系统级上捕获键盘事件而不是通过框架级来实现。为此,应用程序需要使用Windows API函数来把它自身添加到应用程序"钩子链"中以监听来自操作系统的键盘消息。当它收到这种类型的消息时,该应用程序能够选择性地传递消息,或者进行正常处理,或者"镇压"它以便不再有其它应用程序(包括Windows)来影响它。本文正是想解释其实现机理。
然而,请注意,本文中的代码仅适用于基于NT版本的Windows(NT,2000和XP),并且无法使用这个方法来停用Ctrl+Alt+Delete。有关于如何实现这一点,你可以参考MSDN有关资料。
三. 使用代码
为了易于使用,我在本文中提供了两个独立的zip文件。一个仅包含KeyboardHook类,这是本文介绍的重点。另一个是一个完整的微软Visual C# 2005 Express Edition应用程序工程,名叫"Baby Keyboard Bash",它实现显示击键的名字或彩色的形状以响应于击键。
四. 实例化类
键盘钩子是通过keyboard.cs中的KeyboardHook类来建立和管理的。这个类实现了IDisposable接口,因此,实例化它的最简单的方法是在应用程序的Main()方法中使用using关键字来封装Application.Run()调用。这将确保只要该应用程序开始即建立钩子并且,更重要的是,当该应用程序结束时立即使这个钩子失效。
这个类引发一个事件来警告应用程序已经有键被按下,因此主表单能够存取在Main()方法中创建的KeyboardHook实例就显得非常重要;最简单的方法是把这个实例存储在一个公共成员变量中。
KeyboardHook提供了三种构造器来启用或禁用某些设置:
KeyboardHook():捕获所有击键,没有任何内容传递到Windows或另外的应用程序。
KeyboardHook(string param):把参数串转换为Parameters枚举中的值之一,然后调用下面的构造器:
KeyboardHook(KeyboardHook.Parameters enum):根据从Parameters枚举中选择的值的不同,分别启动下列设置:
o Parameters.AllowAltTab:允许用户使用Alt+Tab切换到另外的应用程序。
o Parameters.AllowWindowsKey:允许用户使用Ctrl+Esc或一种Windows键存取任务栏和开始菜单。
o Parameters.AllowAltTabAndWindows:启用Alt+Tab,Ctrl+Esc和Windows键。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)o Parameters.PassAllKeysToNextApp:如果该参数为true,那么所有的击键将被传递给任何其它监听应用程序(包括Windows)。
当击键继续被键盘钩子捕获时,启用Alt+Tab和/或Windows键允许实际使用该计算机者切换到另一个应用程序并且使用鼠标与之交互。PassAllKeysToNextApp设置有效地禁用了击键捕获;这个类也是建立一个低级键盘钩子并且引发它的KeyIntercepted事件,但是它还负责把键盘事件传递到另一个监听程序。
因此,实例化该类以捕获所有击键的方法如下:
public static KeyboardHook kh;[STAThread]static void Main(){ //其它代码 using (kh = new KeyboardHook()) { Application.Run(new Form1()); }
五. 处理KeyIntercepted事件
当一外键被按下时,这个KeyboardHook类激活一个包含一些KeyboardHookEventArgs的KeyIntercepted事件。这是通过一个KeyboardHookEventHandler类型的方法使用以下方式来实现的:
kh.KeyIntercepted += new KeyboardHook.KeyboardHookEventHandler(kh_KeyIntercepted);
这个KeyboardHookEventArgs返回关于被按下键的下列信息:
KeyName:键名,通过把捕获的键代码强制转换为System.Windows.Forms.Keys而获得。
KeyCode:由键盘钩子返回的原来的键代码
PassThrough:指出是否这个KeyboardHook实例被配置以允许该击键传递到其它应用程序。如果你想允许一用户使用Alt+Tab或 Ctrl+Esc/Windows键切换到其它的应用程序的话,那么对之进行检查是很有用的。
然后,使用一个具有适当签名的方法来执行击键所调用的任何任务。下面是一个示例片断:
void kh_KeyIntercepted(KeyboardHookEventArgs e){ //检查是否这个键击事件被传递到其它应用程序并且停用TopMost,以防他们需要调到前端 if (e.PassThrough) { this.TopMost = false; } ds.Draw(e.KeyName);}
本文的剩下部分将解释低级键盘钩子是如何在KeyboardHook中实现的。
六. 实现一个低级Windows API键盘钩子
在user32.dll中,Windows API包含三个方法来实现此目的:
SetWindowsHookEx,它负责建立键盘钩子
UnhookWindowsHookEx,它负责移去键盘钩子
CallNextHookEx,它负责把击键信息传递到下一个监听键盘事件的应用程序
创建一个能够拦截键盘的应用程序的关键是,实现前面两个方法,而"放弃"第三个。结果是,任何击键都只能传递到这个应用程序中。
为了实现这一目标,第一步是包括System.Runtime.InteropServices命名空间并且导入API方法,首先是SetWindowsHookEx:
using System.Runtime.InteropServices...//在类内部:[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]private static extern IntPtr SetWindowsHookEx(int idHook,LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
导入UnhookWindowsHookEx和CallNextHookEx的代码请见后面的讨论。
下一步是调用SetWindowsHookEx来建立钩子,这时需要传递下列四个参数:
idHook:
这个数字决定了要建立的钩子的类型。例如,SetWindowsHookEx可以被用于钩住鼠标事件(当然还有其它事件)。在本文情况下,我们仅对13有兴趣,这是键盘钩子的id。为了使代码更易读些,我们把它赋值给一个常数WH_KEYBOARD_LL。
Lpfn:
这是一个指向函数的长指针,该函数将负责处理键盘事件。在C#中,"指针"是通过传递一个代理类型的实例而获得的,从而使之引用一个适当的方法。这是我们在每次使用钩子时所调用的方法。
这里值得注意的是,这个代理实例需要被存储于这个类的一个成员变量中。这是为了防止一旦第一个方法调用结束它会被作为垃圾回收。
hMod:
建立钩子的应用程序的一个实例句柄。我找到的绝大多数实例仅把它设置为IntPtr.Zero,理由是不大可能存在该应用程序的多个实例。然而,这部分代码使用了来自于kernel32.dll的GetModuleHandle来标识准确的实例从而使这个类更具灵活性。
dwThreadId:
当前进程的id。把它设置为0可以使这个钩子成为全局构子,这是相应于一个低级键盘钩子的正确设置。
SetWindowsHookEx返回一个钩子id,这个id将被用于当应用程序结束时从钩子链中脱钩,因此它需要存储在一个成员变量中以备将来使用。KeyboardHook类中的相关代码如下:
private HookHandlerDelegate proc;private IntPtr hookID = IntPtr.Zero;private const int WH_KEYBOARD_LL = 13;public KeyboardHook(){ proc = new HookHandlerDelegate(HookCallback); using (Process curProcess = Process.GetCurrentProcess()) using (ProcessModule curModule = curProcess.MainModule) { hookID = SetWindowsHookEx(WH_KEYBOARD_LL, proc,GetModuleHandle(curModule.ModuleName), 0); }}(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)