使用 C# 中的钩子






4.58/5 (37投票s)
一篇关于使用 .NET 中的 Windows 钩子的文章,以 MouseHook 为例。
引言
MSDN 对 Windows 钩子的定义是:
钩子是在系统消息处理机制中的一个点,应用程序可以在该点安装一个子程序来监视系统中的消息流,并在某些类型消息到达目标窗口过程之前对其进行处理。
虽然 .NET 框架库已经封装了大部分 Win32 API,但在许多地方我们不得不依赖 P/Invoke 来获取所需的功能。钩子就是其中之一。这可能的一个原因是钩子本身非常底层,大多数程序很少需要。另一个原因是它们与 Windows API 本身的消息驱动性质紧密相关。它们很可能无法移植到其他操作系统。
Windows 钩子是通过回调函数实现的。回调过程的签名类似于 SendMessage
API,因此 Windows 钩子非常通用,可用于扩展多种不同类型的 Windows 消息处理方法(见下文)。该库提供了一个 C# 包装器,用于钩子过程,可以在 Windows Forms 应用程序中使用。
MouseHook
是一种特殊的 Windows 钩子,它允许你的代码在特定线程的任何窗口收到鼠标消息时收到通知。本示例使用鼠标钩子来捕获 5 键鼠标上两个导航按钮的鼠标抬起消息。通过使用鼠标钩子,应用程序的主 Form
可以通过鼠标控制导航,而不管当前哪个子控件获得输入焦点。如果你没有 5 键鼠标,那么演示可能不会让你太兴奋,但代码演示了基本概念。
背景
去年夏天,MSDN 杂志上有一篇由 Dino Esposito 撰写的关于在 C# 中实现钩子的 有趣文章。“嗯,”我想,“这很有趣,但你为什么要这样做呢?”
好吧,我目前正在开发的一个应用程序使用了一种类似网页浏览器的“前进/后退”模式,用户可以在视图之间导航,就像浏览一系列网页一样。每个视图都是一个 UserControl
,它会随着用户在应用程序中移动而动态加载。
我个人已经离不开我的五键鼠标上的前进和后退按钮了,所以我想在我们的应用程序中添加鼠标导航。
我很快意识到这并不像最初看起来那么简单。因为每个视图 UserControl
都占据了它所托管的 Form
的整个客户区,所以 Form
本身从未收到任何 MouseUp
事件。我不想在我创建的每个视图中都处理这个问题,所以必须想一种方法在一个地方处理鼠标按钮导航。
我的第一个想法是在 Form
中附加到每个视图的 MouseUp
事件,并从中进行导航。但这很快就被证明是一种权宜之计,因为每个 UserControl
都有自己的子控件,当子控件获得焦点时,UserControl
也不会收到任何鼠标事件。为了奏效,需要递归地将所有子控件的事件附加到同一个处理程序。听起来很混乱,所以第一个想法被放弃了。
下一个想法是使用 Form
的 WndProc
中的 WM_PARENTNOTIFY
消息。起初这看起来很有希望,而且我很快就有了概念验证。然后,我想:“产生 WM_PARENTNOTIFY
的唯一鼠标消息是鼠标按下消息。我想知道网页浏览器什么时候导航?”
快速测试表明,网页浏览器是在鼠标抬起时导航,而不是鼠标按下时(正如我后来仔细思考后预期的那样)。第二个想法被放弃了。
这让我开始考虑我希望避免深入研究的实现:MouseHook
。我记得(在现在看来就像黑暗时代一样)在 VB6 中实现过这些,以及它们可能给 VB6 IDE 带来的混乱。好吧,这似乎是唯一的解决方案,所以我快速搜索了一下 Google,又回到了 Dino 的文章,快速查看了代码后,它似乎并没有我想象的那么糟糕。
Windows 钩子是线程特定的,此实现使用的是调用 Install
方法的代码所在的线程,如果你从 Form
同步调用它,那么它将是主 UI 线程。这对于我的导航问题来说非常完美,因为现在我可以让 Form
中的代码响应前进/后退鼠标点击,而不管哪个控件具有焦点。
代码下载包含了 Dino Esposito 在 MSDN 示例代码中用于演示 Windows 钩子基础知识的 LocalWindowHook
类,并使用了它。
使用代码
有两种类可用于访问 MouseHook
。
第一个是 MouseHook
类,它直接继承自 LocalWindowHook
。实例化它,附加到它的事件,调用 Install()
,然后就可以开始使用了。当你 Dispose
它或它的 finalizer
运行时,它会移除钩子。
private MouseHook hook = new MouseHook();
private void HookUp()
{
this.hook.Install();
this.hook.MouseUp += new MouseHookEventHandler( this.hook_MouseUp );
}
private void hook_MouseUp(object sender, MouseHookEventArgs e)
{
// do some stuff with your exciting new mouse hook data
}
第二个类是 System.ComponentModel.Component
的派生类型,可以放置在设计图面上。除了使 MouseHook
更容易从 VS.NET IDE 使用之外,MouseHookComponent
还会自动从 Windows Forms 设计器使用的构造函数安装钩子。
这意味着,你真正需要做的就是将其拖放到 Form
上,并附加到你感兴趣的事件。
未来增强功能
Windows API 支持多种不同类型的钩子。Dino Esposito 的 LocalWindowsHook
类是完全通用的,可以用来创建任何一种钩子类型。
public enum HookType : int
{
WH_JOURNALRECORD = 0,
WH_JOURNALPLAYBACK = 1,
WH_KEYBOARD = 2,
WH_GETMESSAGE = 3,
WH_CALLWNDPROC = 4,
WH_CBT = 5,
WH_SYSMSGFILTER = 6,
WH_MOUSE = 7,
WH_HARDWARE = 8,
WH_DEBUG = 9,
WH_SHELL = 10,
WH_FOREGROUNDIDLE = 11,
WH_CALLWNDPROCRET = 12,
WH_KEYBOARD_LL = 13,
WH_MOUSE_LL = 14
}
我创建的 MouseHook
类只是一个派生类示例,它抽象了一个特定类型的 Windows 钩子的复杂性。我还包含了 MSDN 示例代码中的 CbtLocalHook
类。
参考文献
历史
- 2003/09/19 - 初始发布。