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

在 IE 加载项中 Thunking MouseProc

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (7投票s)

2004 年 11 月 3 日

4分钟阅读

viewsIcon

71978

downloadIcon

1051

一篇关于使用 BHO 在 IE 插件中挂钩 MouseProc 的文章。

引言

许多 IE 插件使用 Windows 挂钩机制来监视鼠标消息的通信流量,并执行与鼠标活动相关的预期任务。由于全局挂钩会极大地降低系统性能,因此大多数情况下会使用线程特定的挂钩。

另一方面,IE 是一个多线程 SDI 应用程序。也就是说,只要同一个进程地址空间中的新 IE 实例是从同一个进程地址空间创建的,每个 IE 实例都将在其自己的线程中运行。(您仍然可以通过再次双击桌面上的 IE 图标来创建新的进程地址空间的 IE,或运行命令提示符并键入“IExplore.exe”来创建新的 IE。)

现在,查看 MSDN 以检查挂钩 API 函数的原型

HHOOK SetWindowsHookEx(
  int idHook,        // hook type
  HOOKPROC lpfn,     // hook procedure
  HINSTANCE hMod,    // handle to application instance
  DWORD dwThreadId   // thread identifier
);

您会发现,函数中的第三个参数是线程标识符,并且必须提供该参数,否则它将监视与调用线程位于同一桌面的所有现有线程(这被称为“全局 Windows 挂钩”,对吧?)。

为了安装鼠标挂钩,我们调用 ::SetWindowHookEx() API 函数,并提供 WH_MOUSE 作为挂钩类型,同时提供全局鼠标挂钩过程的地址,该过程是一个应用程序定义的或库定义的 callback 函数,如下所示:

LRESULT CALLBACK MouseProc(
  int nCode,        // hook code
  WPARAM wParam,    // message identifier
  LPARAM lParam)    // mouse coordinates
{
    if (nCode == HC_ACTION)
    {
        if (lParam)
        {
            MOUSEHOOKSTRUCT *pMH = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);

            switch (wParam)
            {
            case WM_LBUTTONDOWN:
            case WM_MBUTTONDOWN:
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDBLCLK:
            case WM_MBUTTONDBLCLK:
            case WM_RBUTTONDBLCLK:
            case WM_MOUSEWHEEL:
                // do something
                break;

            default:
                break;
            }
        }
    }

    return ::CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

为了使窗口挂钩链机制正常工作,您应该在提供的 MouseProc callback 函数中调用 ::CallNextHookEx() API,在处理自己的任务之前或之后。在这里,您可以看到 ::CallNextHookEx() API 函数的第一个参数是当前挂钩的句柄,这正是 ::SetWindowsHookEx() API 的返回值。

LRESULT CallNextHookEx(
  HHOOK hhk,      // handle to current hook
  int nCode,      // hook code passed to hook procedure
  WPARAM wParam,  // value passed to hook procedure
  LPARAM lParam   // value passed to hook procedure
);

此时,您将能够看到全局映射结构在全局 MouseProc callback 函数中查找适当的线程特定 HHOOK 句柄的必要性。映射是一个很好的解决方案,而哈希映射甚至更好,因为其查找成本在最坏情况下已知为 O(1)。因此,我相信大多数 IE 插件在此上下文中都会使用映射结构。

但是,这与 WndProc 和 MFC 的全局 HWND 映射结构情况不是很相似吗?也许我可以使用 ATL 的汇编挂钩技术来提高性能,就像 ATL 在 MFC 上所做的那样。由于 WM_MOUSEMOVE 消息是最频繁的窗口消息之一,即使是最小的查找成本也不值得浪费。无限远征!:P

实现说明

我想您可能已经厌倦了我的糟糕英语,所以我将在这里为您提供参考,以帮助您理解汇编挂钩是什么以及它是如何发挥作用的。

我的挂钩实现的精髓如下所示:

mov    eax, dword ptr [esp+0Ch]            // 8B 44 24 0C
mov    [pThis->;lParam], eax                // A3 [DWORD pThis->lParam]
mov    dword ptr [esp+0Ch], [pThis]        // C7 44 24 0C [DWORD pThis]
jmp    [MouseProc addr]                    // E9 [DWORD MouseProc addr]

我将 MouseProc 更改为接受 C++ 类指针(CMouseProcHook 类)作为参数,而不是 LPARAM。当 BrowserHelperObject 通过调用 IObjectWithSite::SetSite() 连接时,从 ::SetWindowsHookEx() API 函数获得的线程特定 IE 的挂钩句柄将被存储在 C++ 对象中,并且它会将this 指针缓存到某个已知的地方,然后使用特殊的“启动”鼠标挂钩程序安装 Windows 挂钩。在“启动”鼠标挂钩程序中,我创建了一系列汇编语言指令(挂钩),这些指令用this 指针的物理地址(从已知的地方检索)替换了鼠标挂钩程序的 LPARAM 参数,然后跳转到带有修改后的堆栈的“真实”鼠标挂钩程序。在“真实”鼠标挂钩程序中,我们通过简单地将 LPARAM 参数转换为 CMoudeProcHook 来获取 C++ 类,并获取“真实”LPARAM 以及线程特定挂钩的句柄。下图显示了挂钩如何修改调用堆栈然后转发调用。

发现 MouseProcLPARAM 位于 esp + 0Ch,我通过在 MouseProc 代码的第一行设置断点并启用反汇编调试窗口(ALT+8)来仔细检查这一点。

现在,新的 MouseProc 将如下所示更新:

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    CMouseProcHook *pThis = reinterpret_cast<CMouseProcHook *>(lParam);
    lParam = pThis->GetLPARAM();

    if (nCode == HC_ACTION)
    {
        if (lParam)
        {
            MOUSEHOOKSTRUCT *pMH = reinterpret_cast<MOUSEHOOKSTRUCT *>(lParam);

            switch (wParam)
            {
            case WM_LBUTTONDOWN:
            case WM_MBUTTONDOWN:
            case WM_RBUTTONDOWN:
            case WM_LBUTTONDBLCLK:
            case WM_MBUTTONDBLCLK:
            case WM_RBUTTONDBLCLK:
            case WM_MOUSEWHEEL:
                // do something
                break;

            default:
                break;
            }
        }
    }

    return ::CallNextHookEx(pThis->GetHHOOK(), nCode, wParam, lParam);
}

我应该提到的最后一件事是我代码中使用的映射结构。该映射仅用于避免多次挂钩安装和从挂钩链中删除已安装的挂钩程序。其余的故事与 WndProc 挂钩案例相同。有关详细信息,请参阅源代码。

使用代码

编译源代码时,它将自动在 Windows 注册表的 BrowserHelperObject 部分注册输出的 DLL 文件。然后,您可以运行 IE,单击或双击 IE 客户端区域的任何鼠标按钮,这将简单地在 IE 的状态栏中显示预定义的事件。测试完成后,您可以合并包含的注册表文件(“DelBHO.reg”)以手动删除和清理 Windows 注册表中已注册的 BrowseHelperObject 条目。

© . All rights reserved.