What Is a Message Pump

Message pump in .NET Windows service

Thanks all for your suggestions. Richard & overslacked, the link you provided in the comments was very helpful. Also, I did not have to allow the service to interact with the desktop in order to manually start a message pump with Application.Run. Apparently, you only need to allow the service to interact with the desktop if you want Windows to start a message pump automatically for you.

For everyone's edification, here is what I ended up doing to manually start a message pump for this 3rd party API:

internal class MessageHandler : NativeWindow
{
public event EventHandler<MessageData> MessageReceived;

public MessageHandler ()
{
CreateHandle(new CreateParams());
}

protected override void WndProc(ref Message msg)
{
// filter messages here for your purposes

EventHandler<MessageData> handler = MessageReceived;
if (handler != null) handler(ref msg);

base.WndProc(ref msg);
}
}

public class MessagePumpManager
{
private readonly Thread messagePump;
private AutoResetEvent messagePumpRunning = new AutoResetEvent(false);

public StartMessagePump()
{
// start message pump in its own thread
messagePump = new Thread(RunMessagePump) {Name = "ManualMessagePump"};
messagePump.Start();
messagePumpRunning.WaitOne();
}

// Message Pump Thread
private void RunMessagePump()
{
// Create control to handle windows messages
MessageHandler messageHandler = new MessageHandler();

// Initialize 3rd party dll
DLL.Init(messageHandler.Handle);

Console.WriteLine("Message Pump Thread Started");
messagePumpRunning.Set();
Application.Run();
}
}

I had to overcome a few hurdles to get this to work. One is that you need to make certain to create the Form on the same thread that you execute Application.Run. You also can only access the Handle property from that same thread, so I found it easiest to simply initialized the DLL on that thread as well. For all I know, it is expecting to be initialized from a GUI thread anyway.

Also, in my implementation, the MessagePumpManager class is a Singleton instance, so that only one message pump runs for all instances of my device class. Make sure that you truly lazy-initialize your singleton instance if you start the thread in your constructor. If you start the thread from a static context (such as private static MessagePumpManager instance = new MessagePumpManager();) the runtime will never context switch into the newly created thread, and you will deadlock while waiting for the message pump to start.

How to implement a message pump in Non-UI thread in .NET?

Made this a seperate answer for code formatting

Ok so after reading your update I think you want what I describe in the "second case" you simply want

Broadcast<T>("Foo") 

where T is a delegate.

Then your consumer will do

Subscribe<T>("Foo",HandlerMethod)

So a producer consumer scenario would look like this

internal static class MessagePump
{

public static void Subscribe<T>(String foo, Action<String> handlerMethod)
{
throw new NotImplementedException();
}

public static void BroadcastMessage<T>(String foo, Action<String> someAction)
{
throw new NotImplementedException();
}
}

public class Producer
{
void SendMessage()
{
MessagePump.BroadcastMessage<Action<String>>("Foo", SomeAction);
}

void SomeAction(String param)
{
//Do Something
}
}


public class Consumer
{

public Consumer()
{
MessagePump.Subscribe<Action<String>>("Foo", HandlerMethod);
}

void HandlerMethod(String param)
{
// Do Something
}

}

This is just something off the top of my head and is a contrived example so take it with a grain of salt. This is nearly exactly what I am doing in the courier framework I posted earlier. You may want to dive into that code to get a more concrete implementation example.

You need to think about how you will manage consumers, how you validate Broadcast and subscriptions and for your specific case how are you going to ensure the delegate you are passing around is invoked correctly? Or do you care?

Does this help?

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

As IInspectable explained, the modal dialog will run in the same thread as the caller. Therefore if you run the dialog from the main UI thread that has your main message loop, you'll end up with a nested message loop. The stack would look something like:

WinMain
YourMainMessageLoop
DispatchMessage
SomeMessageHandler
DoModal

and DoModal spins in its own GetMessage/TranslateMessage/DispatchMessage loop. The main message loop (YourMainMessageLoop in the sample stack above) is "active" in the sense that it's still running, but it's blocked by the dialog's message loop. Execution won't return to YourMainMessageLoop until DoModal exits.

Note that even if you're within the modal dialog's message loop, your other windows will still handle messages because GetMessage and DispatchMessage will still retrieve and direct messages to those windows and invoke their WndProcs.

How to pump COM messages?

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;

How to keep a thread's message pump reactive

My initial implementation of the message pump used GetMessage like:

while not Terminated and GetMessage(Msg, 0, 0, 0) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;

The problem I found with that, is that GetMessage will never return unless there is a message. Meaning, if there is low message activity, it may be quite a while before it checks Terminated again.

You can override the thread's virtual TerminatedSet() method to post a message to the queue via PostMessage() or PostThreadMessage() to "wake up" GetMessage() if it is blocked.

Alternatively, have your thread constructor create a TEvent object, and free it in the thread's destructor. Then have TerminatedSet() signal that event. Your loop can then use MsgWaitForMultipleObjects() to wait on the message queue AND the event at the same time. The return value will tell you whether the wait was satisfied by a message or the event.

My second implementation (inspired by this answer) used MsgWaitForMultipleObjects to wait until a message exists before checking (since it has a timeout)

while not Terminated do
begin
if MsgWaitForMultipleObjects(0, nil^, False, 1000, QS_ALLEVENTS) = WAIT_OBJECT_0 then
begin
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
end;

The problem I've found with this, is that MsgWaitForMultipleObjects blocks the thread while it waits. So, when a message is sent to the thread via SendMessageTimeout, it times out, where it doesn't when using GetMessage.

The SendMessage...() family of functions will deliver a message directly to the target window's message procedure, bypassing the message queue completely. So MsgWaitForMultipleObjects() and (Get|Peek)Message() will never report a sent message from SendMessage...(), only a posted message from PostMessage() or PostThreadMessage() (or a synthesized message, like WM_TIMER, WM_PAINT, etc). However, when sending a message across thread boundaries, the receiving thread still needs to perform message retrieval calls (is, (Get|Peek)Message()) in order for the sent message to actually be delivered to the window procedure.

The solution that comes to mind is to go back to the GetMessage implementation, but add a timer to make sure a WM_TIMER message resets the loop every second.

Inside a thread, it would be better to use a waitable timer instead of WM_TIMER, then you can use the timer with MsgWaitForMultipleObjects(). But really, there is very little difference between using GetMessage() with WM_TIMER vs MsgWaitForMultipleObjects() with a timeout, so there is no need to waste system resources creating the timer.



Related Topics



Leave a reply



Submit