【问题标题】:MessageBoxEx stops updation of actionsMessageBoxEx 停止更新操作
【发布时间】:2012-12-18 05:58:48
【问题描述】:

我使用 Delphi 7,我的项目有几个非模态可见表单。问题是,如果其中一个调用 MessageBoxEx,则应用程序的所有操作都不会更新,直到 MessageBoxEx 的表单关闭。在我的项目中,它可能会破坏应用程序的业务逻辑。

在显示 MessageBoxEx 的窗口时从不调用 TApplication.HandleMessage 方法,因此它不会调用 DoActionIdle 并且不会更新操作。

我认为我需要在应用程序空闲时捕获它的状态并更新所有操作的状态。

首先我实现了 TApplication。 OnIdle 处理程序:

procedure TKernel.OnIdle(Sender: TObject; var Done: Boolean);
begin
  {It’s only to switch off the standard updating from TApplication.Idle. It's to make the CPU usage lower while MessageBoxEx's window isn't shown }
 Done := False;
end;

implementation

var
  MsgHook: HHOOK;

{Here is a hook}
function GetMsgHook(nCode: Integer; wParam: Longint; var Msg: TMsg): Longint; stdcall;
var
  m: TMsg;
begin
  Result := CallNextHookEx(MsgHook, nCode, wParam, Longint(@Msg));
  if (nCode >= 0) and (_instance <> nil) then
  begin
    {If there aren’t the messages in the application's message queue then the application is in idle state.}
    if not PeekMessage(m, 0, 0, 0, PM_NOREMOVE) then
    begin
      _instance.DoActionIdle;
      WaitMessage;
    end;
  end;
end;

initialization
    MsgHook := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgHook, 0, GetCurrentThreadID);

finalization
  if MsgHook <> 0 then
    UnhookWindowsHookEx(MsgHook);

这是一种更新应用程序所有动作状态的方法。只是 TApplication.DoActionIdle 的修改版:

type
  TCustomFormAccess = class(TCustomForm);

procedure TKernel.DoActionIdle;
var
  i: Integer;
begin
  for I := 0 to Screen.CustomFormCount - 1 do
    with Screen.CustomForms[i] do
      if HandleAllocated and IsWindowVisible(Handle) and
        IsWindowEnabled(Handle) then
        TCustomFormAccess(Screen.CustomForms[i]).UpdateActions;
end;

状态的更新似乎比平时更频繁(我将使用分析器找出问题所在)。

此外,当鼠标光标不在应用程序的窗口上时,CPU 使用率会严重增加(在我的 DualCore Pentium 上约为 25%)。

您如何看待我的问题以及我尝试解决它的方式?使用钩子是个好主意还是有更好的方法来捕获应用程序空闲状态?在设置钩子时我是否更需要使用 WH_CALLWNDPROCRET?

为什么 MessageBoxEx 会阻塞 TApplication.HandleMessage?有没有办法防止这种行为?我尝试使用 MB_APPLMODAL、MB_SYSTEMMODAL、MB_TASKMODAL 标志来调用它,但没有帮助。

【问题讨论】:

    标签: delphi delphi-7


    【解决方案1】:

    MessageBox/Ex() 是一个模态对话框,因此它在内部运行自己的消息循环,因为调用线程的正常消息循环被阻塞。 MessageBox/Ex() 接收调用线程的消息队列中的任何消息,并将它们正常分派到目标窗口(因此基于窗口的计时器之类的东西仍然有效,例如TTimer),但它的模态消息循环没有概念VCL 特定的消息,如操作更新,并将丢弃它们。 TApplication.HandleMessage() 仅被 VCL 主消息循环、TApplication.ProcessMessages() 方法和 TForm.ShowModal() 方法调用(这就是为什么模态 VCL 窗体窗口不会出现此问题的原因),而 MessageBox/Ex() 均未调用正在运行(对于任何操作系统模式对话框都是如此)。

    要解决您的问题,您有几个选择:

    1. 在调用MessageBox/Ex() 之前通过SetWindowsHookEx() 设置线程本地消息挂钩,然后在MessageBox/Ex() 退出后立即释放挂钩。这使您可以查看MessageBox/Ex() 接收的每条消息,并根据需要将它们分派给VCL 处理程序。 请勿在消息挂钩内调用PeekMessage()GetMessage()WaitMessage()

      type
        TApplicationAccess = class(TApplication)
        end;
      
      function GetMsgHook(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
      var
        Msg: TMsg;
      begin
        if (nCode >= 0) and (wParam = PM_REMOVE) then
        begin
          Msg := PMsg(lParam)^;
          with TApplicationAccess(Application) do begin
            if (not IsPreProcessMessage(Msg))
              and (not IsHintMsg(Msg))
              and (not IsMDIMsg(Msg))
              and (not IsKeyMsg(Msg))
              and (not IsDlgMsg(Msg)) then
            begin
            end;
          end;
        end;
        Result := CallNextHookEx(MsgHook, nCode, wParam, lParam);
      end;
      
      function DoMessageBoxEx(...): Integer;
      var
        MsgHook: HHOOK;
      begin
        MsgHook := SetWindowsHookEx(WH_GETMESSAGE, @GetMsgHook, 0, GetCurrentThreadID);
        Result := MessageBoxEx(...);
        if MsgHook <> 0 then UnhookWindowsHookEx(MsgHook);
      end;
      
    2. MessageBox/Ex() 调用移至单独的工作线程,以便调用线程可以正常处理消息。如果需要等待MessageBox/Ex()的结果,比如提示用户输入时,可以使用MsgWaitForMultipleObjects()等待线程终止,同时允许等待线程在有挂起时调用Application.ProcessMessages()要处理的消息。

      type
        TMessageBoxThread = class(TThread)
        protected
          procedure Execute; override;
          ...
        public
          constructor Create(...);
        end;
      
      constructor TMessageBoxThread.Create(...);
      begin
        inherited Create(False);
        ...
      end;
      
      function TMessageBoxThread.Execute;
      begin
        ReturnValue := MessageBoxEx(...);
      end;
      
      function DoMessageBoxEx(...): Integer;
      var
        Thread: TMessageBoxThread;
        WaitResult: DWORD;
      begin
        Thread := TMessageBoxThread.Create(...);
        try
          repeat
            WaitResult := MsgWaitForMultipleObjects(1, Thread.Handle, False, INFINITE, QS_ALLINPUT);
            if WaitResult = WAIT_FAILED then RaiseLastOSError;
            if WaitResult = WAIT_OBJECT_0 + 1 then Application.ProcessMessages;
          until WaitResult = WAIT_OBJECT_0;
          Result := Thread.ReturnVal;
        finally
          Thread.Free;
        end;
      end;
      

    【讨论】:

    • 感谢您的回答!至于我,第一个解决方案看起来更有吸引力。不幸的是,他们都认为我需要找到 MessageBox 的所有调用并替换。但我认为这不会太难。
    • 什么是 IsPreProcessMessage?它是来自任何旧版本 Delphi 的方法吗?
    • 嗯。我认为第一种方法是不正确的,是吗?当队列中没有消息(没有任何 VCL 消息更新操作)时,TApplication 调用 DoActionIdle(更新操作),并且当 MessageBox 的窗口接收到一些消息时执行钩子时,应用程序变得空闲。我猜这不一样。
    • IsPreProcessMessage() 允许 MDI MainForm 或当前关注的 TWinControl 在处理消息时获得第一个 dib。它是在 D2005 中引入的。至于VCL的空闲处理,为了在第一种方案中调用它,可以在需要的时候调用公共的TApplication.DoApplicationIdle()方法。诀窍是确定何时调用它,因为您无法检测到模式对话框的内部消息循环何时空闲。我可能会使用一个短的一次性计时器,其中每条收到的消息(重新)设置计时器,然后在它过去时调用DoApplicationIdle()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多