【问题标题】:Capturing all windows messages to an application将所有 Windows 消息捕获到应用程序
【发布时间】:2021-08-06 09:53:17
【问题描述】:

是否有适用于应用程序中所有窗口的全局 WinProc 或在短时间内捕获应用程序的所有绘制消息的方法?我们使用的一些第三方库将自己的 wndproc 添加到控件(例如 DevExpress 功能区、对接管理器)。

我们有一些代码应该在后台线程中运行,但目前还不可能。此代码可能需要很长时间才能运行,并且显然应用程序在此期间变得无响应。我们不能使用像 processmessages 这样的东西,因为这会导致代码被重新输入。为了解决这个问题,我认为应该可以创建我们自己的 ProcessMessages 版本来查找特定消息。我的想法是,我将创建一个带有进度条和取消按钮的小面板,然后只允许为取消按钮的窗口句柄发送消息。

处理单个消息的简化代码如下所示:

begin
  if PeekMessage(aMsg, 0, 0, 0, PM_NOREMOVE) then
  begin
    Unicode := (aMsg.hwnd = 0) or IsWindowUnicode(aMsg.hwnd);
    if Unicode then
      MsgExists := PeekMessageW(aMsg, 0, 0, 0, PM_REMOVE)
    else
      MsgExists := PeekMessageA(aMsg, 0, 0, 0, PM_REMOVE);

    if MsgExists then
    begin
      Result := True;
      if aMsg.hwnd = aButtonWindowsHandle then
      begin
        TranslateMessage(aMsg);
        if Unicode then
          DispatchMessageW(aMsg)
        else
          DispatchMessageA(aMsg);
      end
      else
      begin
        // WM_PAINT is a special message. If you don't handle a WM_PAINT
        // then windows will just stick it back in the queue again.
        if (aMsg.message = WM_PAINT) and (aMsg.hwnd <> 0) then
        begin
          // Queue this paint message so we do an update when the
          // processing is finished.
          FQueuedPaintMessages.Add(aMsg);
          // Paint nothing here otherwise Windows will send it again and
          // insist that we paint it.
          if BeginPaint(aMsg.hwnd, paintStruct) <> 0 then
            EndPaint(aMsg.hwnd, paintStruct);
        end;
      end;
    end;
  end;
end;

这样调用:

while ProcessMessage(msg) do {loop};

这似乎工作正常,但在测试期间,我发现了几个案例,其中一个 Windows API 调用(PeekMeesage、TranslateMessage、DispatchMessage 或两个绘画调用)正在通过 KiUserCallbackDispatcher 触发对停靠控件的 WndProc 的回调在 ntdll.dll 中。停靠控制最终会触发油漆,这是一个问题。我不确定 Windows API 调用它是什么,因为此时堆栈跟踪缺少一些行。它只发生在我们的一台测试机器上,所以我不能设置断点。

我知道这远非理想的解决方案,我们最好重写代码以便它可以在后台线程中运行,但这将是大量工作,目前不可行.

看起来带有 WH_MOUSE_LL 的 SetWindowsHookEx 可能是一个解决方案,但仍然需要一个消息循环。

【问题讨论】:

  • 通过合理的努力,这些都无法可靠地工作。这只是对抗系统的笨拙尝试,系统将赢得那个。只需修复您的应用程序,不要到处安装笨拙的解决方法,人们随着时间的推移开始使用,然后您无法返回并删除它。您不会通过积累更多的技术债务来对抗技术债务。
  • @IInspectable 有一个项目正在进行中以从 UI 中破解代码,但不幸的是,这是一个为期 1 年的项目。我希望找到一种解决方法,让客户可以在此之前取消长期运行的流程。这肯定不会是人们会随着时间的推移开始使用的东西。
  • 在这一切开始之前显示一个不同的窗口(解释程序变得没有响应),隐藏你当前的窗口,然后让它滚动。之后恢复当前窗口。对于“目前”无法将所有内容塞入自己的线程的借口,这应该是足够的解决方法。
  • 可以只清除更新区域而不是 Begin/EndPaint 对,虽然我不明白这部分代码的用途。
  • 如果要禁止更新窗口内容,可以使用 WM_SETREDRAW。

标签: delphi winapi


【解决方案1】:

与其说是对您的问题的回答,不如说是对您问题的潜在解决方案...

面板没有带有“取消”按钮,而是显示一个标签,上面写着“按住 ESC 取消进程”。然后,在您的进程中,您可以通过定期调用 GetAsyncKeyState 来获取 ESC 键的状态。我没有对其进行测试,但我希望它至少能够“足够好”地满足您的需求。

【讨论】:

  • VBA 始终提供CTRL+Pause 作为中止执行的组合键(与CTRL+C to for console processes in general 相比)。
  • @AmigoJack 我在示例中使用了 ESC,但可以使用任何键/键组合。 ESC 对我来说只是一个更自然的选择。
  • 这非常有效。我们能够检查密钥状态并且已经显示了一条消息。
  • 嗯,这是一种有趣的方法,它允许用户仍然对 UI 等待主线程处理所有窗口消息的应用程序进行一些控制。将在我的一些原型项目中派上用场,我不太关心将冗长的处理转移到单独的线程中。不再需要从 IDE 中终止整个应用程序以取消冗长的处理。
【解决方案2】:

选定的答案为我的问题提供了解决方案,但@IInspectable 回答了有关捕获所有绘制消息的问题。所以对于其他正在看这个问题的人。答案是这样的:

“通过合理的努力,这些都无法可靠地工作。这只是与系统对抗的笨拙尝试,系统将赢得那个。”

换句话说。不要尝试去做。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-29
    • 2016-07-02
    • 2019-08-16
    • 2011-08-26
    • 1970-01-01
    相关资源
    最近更新 更多