SetWindowsHookEx 与 Qt4 中的 WH_JOURNALRECORD 挂钩

Posted

技术标签:

【中文标题】SetWindowsHookEx 与 Qt4 中的 WH_JOURNALRECORD 挂钩【英文标题】:SetWindowsHookEx with WH_JOURNALRECORD hook in Qt4 【发布时间】:2018-04-17 21:12:54 【问题描述】:

我正在调试一个使用 Qt 4.8.6 的闭源遗留应用程序 应用程序在自动更新 Windows 10 后出现问题

当问题被触发时,应用程序(以及整个 Windows 桌面)停止接收键盘和鼠标事件,但应用程序继续运行,鼠标光标也会移动。

如果按下Ctrl+Esc 打开开始菜单,一切都会恢复正常,直到在应用程序中执行特定操作再次触发问题。

我将问题追溯到 Qt 的 QWidget::grabMouse(),它在应用程序中的自定义小部件上调用。

跟踪QWidget::grabMouse()的执行,发现问题发生在执行时:

journalRec = SetWindowsHookEx(WH_JOURNALRECORD, (HOOKPROC)qJournalRecordProc, GetModuleHandle(0), 0);

它的qJournalRecordProc 看起来像这样:

    // The procedure does nothing, but is required for mousegrabbing to work
    LRESULT QT_WIN_CALLBACK qJournalRecordProc(int nCode, WPARAM wParam, LPARAM lParam)
    
        return CallNextHookEx(journalRec, nCode, wParam, lParam);
    

可以在here找到所有定义的QWidget.cpp的完整源代码

谷歌搜索显示SetWindowsHookEx 自 Vista 以来有特殊要求,以阻止恶意软件使用它。该应用程序似乎满足我通过谷歌搜索找到的要求(由受信任的证书签名,尽管它使用 SHA1,安装在“程序文件”中,...)

现在是实际问题:

    为什么 Qt 需要WH_JOURNALRECORD 来执行鼠标抓取?我认为通过使用WH_JOURNALRECORD,钩子程序会获取鼠标/键盘事件,这不会影响单个小部件。我修补了QtGui4.dll,所以它不会调用SetWindowsHookEx(以及相关的Unhook)。这修复了应用程序并且没有任何明显的副作用。

    为什么以这种方式使用WH_JOURNALRECORD 挂钩会停止传递键盘/鼠标事件?我还在钩子程序上设置了一个断点,它似乎永远不会被调用。我还操纵了钩子程序,因此它会使应用程序崩溃(以防这种奇怪的钩子行为弄乱了调试器)并且应用程序没有崩溃,我认为这证实了钩子程序永远不会被调用。

所有这些挂钩的东西看起来像一个真正丑陋的黑客...... 显然 Qt 开发人员认识到了这一点,因为 Qt5 的实现不再使用 SetWindowsHookEx...

编辑(澄清一些 cmets):

我已经修改了 QtGui4 库的grabMouse 不调用SetWindowsHookEx 并且不调用相应的UnhookWindowsHookEx。 由于封闭源应用程序显然使用商业许可下的 Qt 库,他们对 Qt 进行了一些封闭源代码修改,这迫使我实际上修补了他们的 QtGui4.dll(将调用SetWindowsHookEx 的部分更改为NOPs

这解决了应用程序的问题并且不会产生明显的副作用。但我想知道为什么 Qt 开发人员认为有必要首先将钩子(什么都不做)放在那里。是什么让它行为不端。

【问题讨论】:

我不喜欢 qJournalRecordProc 上的演员表。 嗯... Qt 人们似乎喜欢它。它被定义为 typedef LRESULT (CALLBACK* HOOKPROC)(int code, WPARAM wParam, LPARAM lParam);在 WinUser.h... JournalRecordProc 具有相同的签名......所以这应该没问题 如果签名正确,则不需要演员表。 C 强制转换隐藏调用约定错误! 当然...但这不是我的代码 :) 这是 Qt 框架本身! 【参考方案1】:

您说您正在修补 QtGui4.dll,但您的 SetWindowsHookEx 行包含 GetModuleHandle(0) 表明 qJournalRecordProc 在您的 .EXE 中!

全局挂钩应在 .DLL 中实现,并且应将正确的 HINSTANCE 传递给 SetWindowsHookEx

【讨论】:

请参阅 MSDN 文档的备注部分以了解 JournalRecordProc 回调:JournalRecordProc 挂钩过程不需要存在于动态链接库中。 JournalRecordProc 挂钩过程可以存在于应用程序本身中。 另外这不是我的 SetWindowsHookEx,这是在 QtGui4.dll 的实现中,我也认为这可能是一个问题,但相同的代码在旧 Windows 版本上运行良好。 嗯,问题是它/你传递的 HINSTANCE 不是回调所在的模块。 好吧.. 只是为了好玩,我可以尝试解决这个问题,但问题仍然存在。为什么它会这样?如果回调的地址错误,为什么它根本不返回错误或崩溃?为什么它从 Windows XP 开始就可以使用? 日志挂钩很旧,一直到 16 位 Windows (IIRC),并且可能有相当多的兼容性内容可以帮助损坏的应用程序。对于任何类型的实时鼠标抓取/监控来说,这也是错误的选择,但我猜这是另一个话题。

以上是关于SetWindowsHookEx 与 Qt4 中的 WH_JOURNALRECORD 挂钩的主要内容,如果未能解决你的问题,请参考以下文章

增加 Windows 7 中的 SetWindowsHookEx 限制

SetWindowsHookEx - 防病毒问题

SetWindowsHookEx WH_MOUSE 在 Win7 中的断点处冻结

QTabBar中的Qt4扩展选项卡

在我的线程上下文中调用“SetWindowsHookEx”函数

SetWindowsHookEx 注入失败