在我的代码完成运行之前,如何防止事件被传递到 GUI?

Posted

技术标签:

【中文标题】在我的代码完成运行之前,如何防止事件被传递到 GUI?【英文标题】:How can I keep an event from being delivered to the GUI until my code finished running? 【发布时间】:2010-05-26 12:41:31 【问题描述】:

我这样安装了一个全局鼠标钩子函数:

mouseEventHook = ::SetWindowsHookEx( WH_MOUSE_LL, mouseEventHookFn, thisModule, 0 );

钩子函数如下所示:

RESULT CALLBACK mouseEventHookFn( int code, WPARAM wParam, LPARAM lParam )

    if ( code == HC_ACTION ) 
        PMSLLHOOKSTRUCT mi = (PMSLLHOOKSTRUCT)lParam;
        // .. do interesting stuff ..
    
    return ::CallNextHookEx( mouseEventHook, code, wParam, lParam );

现在,我的问题是我无法准确控制“做有趣的事情”部分需要多长时间。特别是,它可能需要比 Windows 注册表中定义的 LowLevelHooksTimeout 更长的时间。这意味着,至少在 Windows XP 上,系统不再将鼠标事件传递给我的钩子函数。我想避免这种情况,但同时我需要“做一些有趣的事情”部分在目标 GUI 接收到事件之前发生。

我试图通过在单独的线程中完成“有趣的事情”来解决这个问题,以便上面的 mouseEventHookFn 可以向工作线程发布消息,然后立即执行 return 1;(这会结束钩子函数但避免将事件交给 GUI)。这个想法是,工作线程在完成后会自行执行CallNextHookEx 调用。

但是,这会导致CallNextHookEx 内部崩溃(实际上,崩溃发生在名为PhkNextValid 的内部函数内部。我认为从挂钩函数外部调用CallNextHookEx 是不安全的,这是真的吗?

如果是这样,是否有其他人知道我如何在 GUI 接收事件之前运行代码(需要与应用程序的 GUI 线程交互)并且避免我的钩子函数阻塞太久?

【问题讨论】:

【参考方案1】:

假设从钩子函数外部调用 CallNextHookEx 是不安全的,这是真的吗?

我相信这是真的。

由于您可以通过低级鼠标钩子接收的操作数量有限,因此您可以将它们放入队列中,以便在长时间运行的操作完成后重新发布到接收窗口。如果你把你的长时间运行放在另一个线程上,你不会“锁定”用户界面,而只是“吃”或“推迟”用户操作。返回 1 以防止发生其他钩子。使用布尔标志来表示您是在收集事件(因为您的长时间运行的操作必须运行)还是重新发布它们(因此不应挂钩它们)。

系统中不太可能有 (m) 任何其他您要取消的低级挂钩,但您应该在您的情况下彻底测试此机制。我只使用它来阻止之前的操作(杀死鼠标右键单击)而不是推迟它们。

【讨论】:

+1:这基本上是我最初的想法,只是您不清楚如何“重新发布”低级挂钩捕获的事件。我试图用CallNextHookEx 来做这件事——没有成功。 :-) 有没有一种简单(准确)的方法来推断重现该动作的SendInput 调用?值得一提的是,MSLLHOOKSTRUCT 结构(LowLevelMouseProc 回调的一个参数)似乎已经有一个字段来说明它是“真实”事件还是 SendInput 生成的事件。所以我不需要维护自己的旗帜。 :-) 你的钩子在鼠标活动被发布到线程的窗口消息队列之前拦截了它,所以你可以做 PostThreadMessage() 因为你有这个 API 需要的所有信息但还没有 HWND (所以不能做 PostMessage()) 对,但是在我拦截鼠标活动的时候,还没有生成任何窗口消息。我所拥有的只是一个指向MSLLHOOKSTRUCT 结构的指针。为了使用PostThreadMessage(),我需要找出给定的鼠标事件触发了哪些消息(我认为这是棘手的部分)我需要使用消息参数填充一个结构并将其作为@987654327 传递@ 争论。你是这个意思吗?听起来容易出错。 :- 将信息从 LPARAM 复制到您自己的机制 - 如果您在 C++ 领域,类似 map > > 就可以了。或者,使用 WM_COPYDATA 向您自己的私有线程发送消息以克隆 MSLLHOOKSTRUCT 或使用共享内存技术。【参考方案2】:

没有解决办法,你必须让你的代码更快。这些钩子可能对用户界面响应非常有害,Windows 确保将行为不端的钩子放入地下室。即使超时是可配置的,也永远不会记录在案。这会破坏首先设置超时的目的。

【讨论】:

嗯,实际上-根据MSDN的LowLevelKeyboardProc页面,超时实际上是可以在注册表中进行读写的。密钥是HKEY_CURRENT_USER\Control Panel\Desktop\LowLevelHooksTimeout【参考方案3】:

为什么要使用鼠标事件挂钩?您是在一般情况下挂鼠标还是只为特定窗口挂鼠标?如果它是针对特定窗口的,那么您需要 - 而不是使用挂钩 - 实际上是目标窗口的子类。

这通常是一个 2 阶段的过程 - 钩子总是需要在 dll 中,因为钩子需要在实际处理消息的进程(和线程)的上下文中执行。

因此,您首先编写一个钩子 dll,当发送消息时,它会在 HWND 上调用 SetWindowLong 以用您的新窗口 proc 替换 GWL_WINDOWPROC。

在您的 WindowProc 中,您可以根据需要处理消息。

【讨论】:

+1 真;我正在使用全局鼠标事件挂钩,因为我需要在多个窗口中跟踪鼠标操作(可以在运行时更改),而且我懒得做簿记工作来检查我已经子类化了哪些窗口并在它们之间进行 IPC钩子 DLL(当然应该非常小)和我的代码(实际工作)。也许这是唯一正确的解决方案。

以上是关于在我的代码完成运行之前,如何防止事件被传递到 GUI?的主要内容,如果未能解决你的问题,请参考以下文章

如何在codebehind的pageload事件被触发之前,在页面加载时将一个会话存储值传递到文本框中?

数据库调用会在触发器完成之前返回吗?

检测 PowerModeChange 并等待执行

如何防止在动画完成之前点击 html 元素?

jquery如何animate防止叠加 例如我把鼠标不断移入移出 效果就会不断重复,如何防止呢?

如何将环境变量传递给pytest