public static KeyboardHook kh;
[STAThread]
static void Main()
{
//其它代码
using (kh = new KeyboardHook())
{
Application.Run(new Form1());
}
kh.KeyIntercepted += new KeyboardHook.KeyboardHookEventHandler(kh_KeyIntercepted);
void kh_KeyIntercepted(KeyboardHookEventArgs e)
{
//检查是否这个键击事件被传递到其它应用程序并且停用TopMost,以防他们需要调到前端
if (e.PassThrough)
{
this.TopMost = false;
}
ds.Draw(e.KeyName);
}
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);
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);
}
}
七. 处理键盘事件
如前面所提及,SetWindowsHookEx需要一个到被用来处理键盘事件的回调函数的指针。它期望有一个使用如下签名的函数:
LRESULT CALLBACK LowLevelKeyboardProc( int nCode,WPARAM wParam,LPARAM lParam);
其实,建立一个函数指针的C#方法使用了一个代理,因此,向SetWindowsHookEx指出它需要的内容的第一步是使用正确的签名来声明一个代理:
private delegate IntPtr HookHandlerDelegate(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam);
然后,使用相同的签名编写一个回调方法;这个方法将包含实际上处理键盘事件的所有代码。在KeyboardHook的情况下,它检查是否击键应该被传递给其它应用程序并且接下来激发KeyIntercepted事件。下面是一个简化版本的不带有击键处理代码的情况:
private const int WM_KEYDOWN = 0x0100;
private const int WM_SYSKEYDOWN = 0x0104;
private IntPtr HookCallback(int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT lParam)
{
//仅为KeyDown事件过滤wParam,否则该代码将再次执行-对于每一次击键(也就是,相应于KeyDown和KeyUp)
//WM_SYSKEYDOWN是捕获Alt相关组合键所必需的
if (nCode >= 0 && (wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN))
{
//激发事件
OnKeyIntercepted(new KeyboardHookEventArgs(lParam.vkCode, AllowKey));
//返回一个"哑"值以捕获击键
return (System.IntPtr)1;
}
//事件没有被处理,把它传递给下一个应用程序
return CallNextHookEx(hookID, nCode, wParam, ref lParam);
}
接下来,一个到HookCallback的参考被指派给HookHandlerDelegate的一个实例并且被传递到SetWindowsHookEx的调用,正如前一节所展示的。
无论何时一个键盘事件发生,下列参数将被传递给HookCallBack:
・ nCode:
根据MSDN文档,回调函数应该返回CallNextHookEx的结果,如果这个值小于零的话。正常的键盘事件将返回一个大于或等于零的nCode值。
・ wParam:
这个值指示发生了什么类型的事件:键被按下还是松开,以及是否按下的键是一个系统键(左边或右边的Alt键)。
・ lParam:
这是一个存储精确击键信息的结构,例如被按键的代码。在KeyboardHook中声明的这个结构如下:
private struct KBDLLHOOKSTRUCT
{
public int vkCode;
int scanCode;
public int flags;
int time;
int dwExtraInfo;
}
其中的这两个公共参数是在KeyboardHook中的回调方法所使用的仅有的两个参数。vkCoke返回虚拟键代码,它能够被强制转换为System.Windows.Forms.Keys以获得键名,而flags显示是否这是一个扩展键(例如,Windows Start键)或是否同时按下了Alt键。有关于Hook回调方法的完整代码展示在每一种情况下要检查哪些flags值。
如果flags提供的信息和KBDLLHOOKSTRUCT的其它组成元素不需要,那么这个回调方法和代码的签名可以按如下进行修改:
private delegate IntPtr HookHandlerDelegate(
int nCode, IntPtr wParam, IntPtr lParam);
在这种情况中,lParam将仅返回vkCode。
八. 把击键传递到下一个应用程序
一个良好的键盘钩子回调方法应该以调用CallNextHookEx函数并且返回它的结果结束。这可以确保其它应用程序能够有机会处理针对于它们的击键。
然而,KeyboardHook类的主要功能在于,阻止击键被传播到任何其它更多的应用程序。因此它无论在何时处理一次击键,HookCallback都将返回一个哑值:
return (System.IntPtr)1;
另一方面,它确实调用CallNextHookEx-如果它不处理该事件,或如果重载的构造器中的使用KeyboardHook传递的参数允许某些组合键通过。
CallNextHookEx被启用-通过从user32.dll导入该函数,如下列代码所示:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, ref KeyInfoStruct lParam);
然后,被导入的方法被HookCallMethod所调用,这可以确保所有的通过钩子接收到的参数被继续传递到下一个应用程序中:
CallNextHookEx(hookID, nCode, wParam, ref lParam);
如前面所提及,如果在lParam中的flags是不相关的,那么可以修改导入的CallNextHookEx的签名以把lParam定义为System.IntPtr。
九. 移去钩子
处理钩子的最后一步是使用从user32.dll中导入的UnhookWindowsHookEx函数移去它(当破坏KeyboardHook类的实例时),如下所示:
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
既然KeyboardHook实现IDisposable,那么这可以在Dispose方法中完成。
public void Dispose()
{
UnhookWindowsHookEx(hookID);
}
hookID是构造器在调用SetWindowsHookEx所返回的id。这将从钩子链中删除应用程序。
本文出自 “青峰” 博客,转载请与作者联系!