65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 中的钩子

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.58/5 (37投票s)

2003年9月23日

CPOL

5分钟阅读

viewsIcon

519975

downloadIcon

21260

一篇关于使用 .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 也不会收到任何鼠标事件。为了奏效,需要递归地将所有子控件的事件附加到同一个处理程序。听起来很混乱,所以第一个想法被放弃了。

下一个想法是使用 FormWndProc 中的 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 - 初始发布。
© . All rights reserved.