如何提取COM消息?

时间:2015-12-22 17:01:57

标签: windows delphi winapi events com

我想等待 WebBrowser 控件完成导航。所以我创建了一个事件,然后我想等待它被设置:

procedure TContoso.NavigateToEmpty(WebBrowser: IWebBrowser2);
begin
   FEvent.ResetEvent;
   WebBrowser.Navigate2('about:blank'); //Event is signalled in the DocumentComplete event

   Self.WaitFor;
end;

然后我在DocumentComplete事件中设置了事件:

procedure TContoso.DocumentComplete(ASender: TObject; const pDisp: IDispatch; const URL: OleVariant);
var
    doc: IHTMLDocument2;
begin
    if (pDisp <> FWebBrowser.DefaultInterface) then
    begin
       //This DocumentComplete event is for another frame
       Exit;
    end;

    //Set the event that it's complete
    FEvent.SetEvent;
end;

问题在于如何等待此事件发生。

等待

第一反应是等待事件被触发:

procedure TContoso.WaitFor;
begin
   FEvent.WaitFor;
end;

问题在于DocumentComplete事件永远不会触发,因为应用程序永远不会空闲以允许COM事件通过。

忙碌的睡眠等待

我的第一反应是忙着睡觉,等待一面旗帜:

procedure TContoso.NavigateToEmpty(WebBrowser: IWebBrowser2);
begin
   FIsDocumentComplete := False;
   WebBrowser.Navigate2('about:blank'); //Flag is set in the DocumentComplete event
   Self.WaitFor;
end;

procedure TContoso.WaitFor;
var
   n: Iterations;
const
   MaxIterations = 25; //100ms each * 10 * 5 = 5 seconds
begin
   while n < MaxIterations do
   begin
      if FIsDocumentComplete then
         Exit;
      Inc(n);
      Sleep(100); //100ms
   end;
end;

Sleep的问题在于,它不允许应用程序空闲以允许COM事件消息通过。

使用CoWaitForMultipleHandles

经过研究,似乎COM人员创建了一个完全针对这种情况创建的函数:

  

当单线程单元(STA)中的线程阻塞时,我们会为您提供某些消息。阻止期间的消息是微软的黑人艺术之一。过度抽吸会导致重入,从而使应用程序所做的假设无效。泵送太少会导致死锁。从Windows 2000开始,OLE32公开CoWaitForMultipleHandles,以便您可以“恰到好处”。

所以我试过了:

procedure TContoso.WaitFor;
var
   hr: HRESULT;
   dwIndex: DWORD;
begin
   hr := CoWaitForMultipleHandles(0, 5000, 1, @FEvent.Handle, {out}dwIndex);
   OleCheck(hr);
end;

问题是只是不起作用;它不允许出现COM事件。

使用UseCOM等待

我也可以尝试使用Delphi自己的TEvent秘密功能:UseCOMWait

  

UseCOMWait设置为 True ,以确保当线程被阻塞并等待对象时,可以将任何STA COM调用返回到此线程中。

出色!让我们用:

FEvent := TEvent.Create(True);

function TContoso.WaitFor: Boolean;
begin
   FEvent.WaitFor;
end;

除非不起作用;因为回调事件永远不会被触发。

MsgWaitForMultipleBugs

所以现在我开始深入研究可怕的,糟糕的糟糕的 糟糕 ,错误,错误-prone,re-entrancy inducing,sloppy,需要鼠标轻推,有时会崩溃MsgWaitForMultipleObjects的世界:

function TContoso.WaitFor: Boolean;
var
//  hr: HRESULT;
//  dwIndex: DWORD;
//  msg: TMsg;
    dwRes: DWORD;
begin
//  hr := CoWaitForMultipleHandles(0, 5000, 1, @FEvent.Handle, {out}dwIndex);
//  OleCheck(hr);
//  Result := (hr = S_OK);

    Result := False;
    while (True) do
    begin
        dwRes := MsgWaitForMultipleObjects(1, @FEvent.Handle, False, 5000, QS_SENDMESSAGE);
        if (dwRes = WAIT_OBJECT_0) then
        begin
            //Our event signalled
            Result := True;
            Exit;
        end
        else if (dwRes = WAIT_TIMEOUT) then
        begin
            //We waited our five seconds; give up
            Exit;
        end
        else if (dwRes = WAIT_ABANDONED_0) then
        begin
            //Our event object was destroyed; something's wrong
            Exit;
        end
        else if (dwRes = (WAIT_OBJECT_0+1)) then
        begin
            GetMessage(msg, 0, 0, 0);
        if msg.message = WM_QUIT then
        begin
            {
                http://blogs.msdn.com/oldnewthing/archive/2005/02/22/378018.aspx

                PeekMessage will always return WM_QUIT. If we get it, we need to
                cancel what we're doing and "re-throw" the quit message.

                    The other important thing about modality is that a WM_QUIT message
                    always breaks the modal loop. Remember this in your own modal loops!
                    If ever you call the PeekMessage function or The GetMessage
                    function and get a WM_QUIT message, you must not only exit your
                    modal loop, but you must also re-generate the WM_QUIT message
                    (via the PostQuitMessage message) so the next outer layer will
                    see the WM_QUIT message and do its cleanup as well. If you fail
                    to propagate the message, the next outer layer will not know that
                    it needs to quit, and the program will seem to "get stuck" in its
                    shutdown code, forcing the user to terminate the process the hard way.
            }
            PostQuitMessage(msg.wParam);
            Exit;
        end;
        TranslateMessage(msg);
        DispatchMessage(msg);
    end;
end;

以上代码错误,因为:

  • 我不知道要唤醒什么样的消息(是否发送了com事件?)
  • 我不知道我不想调用GetMessage,因为它会收到消息;我只想获得COM消息(见第一点)
  • 我可能应该使用PeekMessage(参见第2点)
  • 我不知道是否必须在循环中调用GetMessage,直到它返回false(参见Old New Thing

我已经编程了足够长的时间逃跑,远离我,如果我要自己发消息。

问题

所以我有四个问题。所有相关的。这篇文章是四个中的一个:

  • 如何使WebBrower.Navigate2同步?
  • 如何提取COM消息?
  • 抽COM消息会导致COM事件回调吗?
  • 如何使用CoWaitForMultipleHandles

我正在写作,并使用Delphi。但显然任何本机代码都可以工作(C,C ++,汇编,机器代码)。

另见

1 个答案:

答案 0 :(得分:5)

短期和长期是你必须正常地输出所有消息,你不能自己单独输出COM消息(此外,没有记录的消息,你可以自己偷看/抽水,它们是只知道COM的内部人员。)

  

如何使WebBrower.Navigate2同步?

你做不到。但您也不必等待OnDocumentComplete事件。您可以在NavigateToEmpty()内部忙碌循环,直到WebBrowser的ReadyState属性为READYSTATE_COMPLETE,在等待处理消息时抽取消息队列:

procedure TContoso.NavigateToEmpty(WebBrowser: IWebBrowser2);
begin
  WebBrowser.Navigate2('about:blank');
  while (WebBrowser.ReadyState <> READYSTATE_COMPLETE) and (not Application.Terminated) do
  begin
    // if MsgWaitForMultipleObjects(0, Pointer(nil)^, False, 5000, QS_ALLINPUT) = WAIT_OBJECT_0 then
    // if GetQueueStatus(QS_ALLINPUT) <> 0 then
      Application.ProcessMessages;
  end;
end;
  

如何提取COM消息?

你不能,不管他自己。泵送所有东西,并准备好处理由此产生的任何再入问题。

  

泵送COM消息会导致COM事件回调吗?

  

如何使用CoWaitForMultipleHandles

尝试这样的事情:

procedure TContoso.NavigateToEmpty(WebBrowser: IWebBrowser2);
var
  hEvent: THandle;
  dwIndex: DWORD;
  hr: HRESULT;
begin
  // when UseCOMWait() is true, TEvent.WaitFor() does not wait for, or
  // notify, when messages are pending in the queue, so use
  // CoWaitForMultipleHandles() directly instead.  But you have to still
  // use a waitable object, just don't signal it...
  hEvent := CreateEvent(nil, True, False, nil);
  if hEvent = 0 then RaiseLastOSError;
  try
    WebBrowser.Navigate2('about:blank');
    while (WebBrowser.ReadyState <> READYSTATE_COMPLETE) and (not Application.Terminated) do
    begin
      hr := CoWaitForMultipleHandles(COWAIT_INPUTAVAILABLE, 5000, 1, hEvent, dwIndex);
      case hr of
        S_OK: Application.ProcessMessages;
        RPC_S_CALLPENDING, RPC_E_TIMEOUT: begin end;
      else
        RaiseLastOSError(hr);
      end;
    end;
  finally
    CloseHandle(hEvent);
  end;
end;