How to pump COM messages?

Ian Boyd

I want to wait for a WebBrowser control to finish navigation. So i create an Event, and then i want to wait for it to be set:

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

   Self.WaitFor;
end;

And then i set the event in the DocumentComplete event:

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;

The problem comes in how to wait for this event to happen.

WaitFor it

First reaction would be to wait for the event to become triggered:

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

The problem with that is that the DocumentComplete event can never fire, because the application never goes idle enough to allow the COM event to get through.

Busy Sleep Wait

My first reaction was to do a busy sleep, waiting for a flag:

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;

The problem with a Sleep, is that it doesn't allow the application to do idle enough to allow the COM event messages to get through.

Use CoWaitForMultipleHandles

After research, it seems that COM folks created a function created exactly for this situation:

While a thread in a Single-Threaded Apartment (STA) blocks, we will pump certain messages for you. Message pumping during blocking is one of the black arts at Microsoft. Pumping too much can cause reentrancy that invalidates assumptions made by your application. Pumping too little causes deadlocks. Starting with Windows 2000, OLE32 exposes CoWaitForMultipleHandles so that you can pump “just the right amount.”

So i tried that:

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

The problem is that just doesn't work; it doesn't allow the COM event to appear.

Use UseCOMWait wait

i could also try Delphi's own mostly secret feature of TEvent: UseCOMWait

Set UseCOMWait to True to ensure that when a thread is blocked and waiting for the object, any STA COM calls can be made back into this thread.

Excellent! Lets use that:

FEvent := TEvent.Create(True);

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

Except that doesn't work; because the callback event never gets fired.

MsgWaitForMultipleBugs

So now i start to delve into the awful, awful, awful, awful, buggy, error-prone, re-entrancy inducing, sloppy, requires a mouse nudge, sometimes crashes world of 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;

The above code is wrong because:

  • i don't know what kind of message to wake up for (are com events sent?)
  • i don't know i don't want to call GetMessage, because that gets messages; i only want to get the COM message (see point one)
  • i might should be using PeekMessage (see point 2)
  • i don't know if i have to call GetMessage in a loop until it returns false (see Old New Thing)

I've been programming long enough to run away, far away, if i'm going to pump my own messages.

The questions

So i have four questions. All related. This post is one of the four:

  • How to make WebBrower.Navigate2 synchronous?
  • How to pump COM messages?
  • Does pumping COM messages cause COM events to callback?
  • How to use CoWaitForMultipleHandles

I am writing in, and using Delphi. But obviously any native code would work (C, C++, Assembly, Machine code).

See also

Remy Lebeau

The short and long of it is that you have to pump ALL messages normally, you can't just single out COM messages by themselves (and besides, there is no documented messages that you can peek/pump by themselves, they are known only to COM's internals).

How to make WebBrower.Navigate2 synchronous?

You can't. But you don't have to wait for the OnDocumentComplete event, either. You can busy-loop inside of NavigateToEmpty() itself until the WebBrowser's ReadyState property is READYSTATE_COMPLETE, pumping the message queue when messages are waiting to be processed:

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;

How to pump COM messages?

You can't, not by themselves anyway. Pump everything, and be prepared to handle any reentry issues that result from that.

Does pumping COM messages cause COM events to callback?

Yes.

How to use CoWaitForMultipleHandles

Try something like this:

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;

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Which blocking operations cause an STA thread to pump COM messages?

From Dev

How to post messages to an STA thread running a message pump?

From Dev

How do I pump window messages in a nodejs addon?

From Dev

How to post messages to an STA thread running a message pump?

From Dev

Delphi - Message pump in thread not receiving WM_COPYDATA messages

From Dev

Hooking into message pump of STA Apartment COM object activated in DllHost by DllSurrogate

From Dev

Hooking into message pump of STA Apartment COM object activated in DllHost by DllSurrogate

From Dev

How does a modal dialog's message-pump interact with the main application message-pump?

From Dev

How to change Oracle default data pump directory to import dumpfile?

From Dev

How Can i pace a producer actor so that it does not pump to many message to a consumer actor

From Dev

How Can i pace a producer actor so that it does not pump to many message to a consumer actor

From Dev

How are Akka messages ordered if messages are not reliable?

From Dev

How to hide <p:messages> after it displayed the messages?

From Dev

How to stack messages with PrimeFaces messages component?

From Dev

How to stack messages with PrimeFaces messages component?

From Dev

How are Akka messages ordered if messages are not reliable?

From Dev

How to receive MSMQ Messages

From Dev

How to display messages in SBT?

From Dev

How to rollback messages in Activemq

From Dev

How to requeue messages in RabbitMQ

From Dev

How to clear Query Messages?

From Dev

How to encrypt msmq messages?

From Dev

How to remove error messages?

From Dev

How to stop logcat messages

From Dev

How to rollback messages in Activemq

From Dev

How to requeue messages in RabbitMQ

From Dev

How to remove error messages

From Dev

How to clear Query Messages?

From Dev

How to pass messages in codeigniter?

Related Related

  1. 1

    Which blocking operations cause an STA thread to pump COM messages?

  2. 2

    How to post messages to an STA thread running a message pump?

  3. 3

    How do I pump window messages in a nodejs addon?

  4. 4

    How to post messages to an STA thread running a message pump?

  5. 5

    Delphi - Message pump in thread not receiving WM_COPYDATA messages

  6. 6

    Hooking into message pump of STA Apartment COM object activated in DllHost by DllSurrogate

  7. 7

    Hooking into message pump of STA Apartment COM object activated in DllHost by DllSurrogate

  8. 8

    How does a modal dialog's message-pump interact with the main application message-pump?

  9. 9

    How to change Oracle default data pump directory to import dumpfile?

  10. 10

    How Can i pace a producer actor so that it does not pump to many message to a consumer actor

  11. 11

    How Can i pace a producer actor so that it does not pump to many message to a consumer actor

  12. 12

    How are Akka messages ordered if messages are not reliable?

  13. 13

    How to hide <p:messages> after it displayed the messages?

  14. 14

    How to stack messages with PrimeFaces messages component?

  15. 15

    How to stack messages with PrimeFaces messages component?

  16. 16

    How are Akka messages ordered if messages are not reliable?

  17. 17

    How to receive MSMQ Messages

  18. 18

    How to display messages in SBT?

  19. 19

    How to rollback messages in Activemq

  20. 20

    How to requeue messages in RabbitMQ

  21. 21

    How to clear Query Messages?

  22. 22

    How to encrypt msmq messages?

  23. 23

    How to remove error messages?

  24. 24

    How to stop logcat messages

  25. 25

    How to rollback messages in Activemq

  26. 26

    How to requeue messages in RabbitMQ

  27. 27

    How to remove error messages

  28. 28

    How to clear Query Messages?

  29. 29

    How to pass messages in codeigniter?

HotTag

Archive