Message pump in .NET Windows service
Asked Answered
W

3

29

I have a Windows Service written in C# that handles all of our external hardware I/O for a kiosk application. One of our new devices is a USB device that comes with an API in a native DLL. I have a proper P/Invoke wrapper class created. However, this API must be initialized with an HWnd to a windows application because it uses the message pump to raise asynchronous events.

Besides putting in a request to the hardware manufacturer to provide us with an API that does not depend on a Windows message pump, is there any way to manually instantiate a message pump in a new thread in my Windows Service that I can pass into this API? Do I actually have to create a full Application class, or is there a lower level .NET class that encapsulates a message pump?

Winterwinterbottom answered 14/3, 2010 at 21:33 Comment(7)
Check out this thread, it may prove helpful: connect.microsoft.com/VisualStudio/feedback/details/241133/…Bluenose
@overslacked, it does indeed. It says exactly how to do it.Karee
@Bluenose I know this is an old question, so it's no surprise that the MS Connect link no longer works. But as the content of that link seems to be crucial to the discussion here, I was wondering if there's a fresh link available. Sorry to be causing a hassle!Boisterous
@Boisterous - can you post a new question, linking back to this one, demonstrating which parts of the accepted answer to this question you've tried, but have not worked? I'll keep an eye on your questions and see if I can help; otherwise, I think the accepted answer illustrates the solution pretty well.Bluenose
@Bluenose I have no problem w/ the accepted answer. I am just curious about the content of the Connect link.Boisterous
@Boisterous - I see! In that case, it looks like archive.org has a copy from 2014-07-06 - web.archive.org/web/20140706130218/http://connect.microsoft.com/…Bluenose
@Bluenose A huge THANK YOU! This completes the puzzle.Boisterous
W
43

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.

Winterwinterbottom answered 16/3, 2010 at 19:33 Comment(4)
Thanks for taking the time to post a solution to your problem - you just saved me hours of work.Proudlove
Thanks... seems to work, however in my case, as my 3rd DLL publishes .net events and those events are raised in another thread, things don't work as expected...Plumper
Issue-1: StartMessagePump is not the constructor, hence unable to initialize the readonly member.Doralin
@Doralin I wrote this code almost 10 years ago using .NET 2.0 (maybe 3.5?). I suspect some things will have to be modified for current versions. Either create a constructor to initialize the Thread object, or remove the readonly if it's an issue.Winterwinterbottom
D
3

Just make a message-only window, denoted by the HWND_MESSAGE parameter in the call to CreateWindowEx. Granted, this is C code, but you can easily make these structs and P/Invoke calls in C#.

WNDCLASS w;
HWND handle;
w.hInstance = (HINSTANCE)GetModuleHandle(...); // Associate this module with the window.
w.lpfnWndProc = ... // Your windowproc
w.lpszClassName = ... // Name of your window class

RegisterClass(&w)
handle = CreateWindowEx(0, w.lpszClassName, w.lpszClassName, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, wc.hInstance, NULL);
Dasha answered 14/3, 2010 at 21:45 Comment(7)
Certainly don't need to use P/Invoke calls to make a window.Karee
I don't think you can make a HWND_MESSAGE window without a call to CreateWindowEx.Dasha
I have no problems going this route as long as the message pump will run from a windows service that is not configured to interact with the desktop. I'll give it a shot tomorrow.Winterwinterbottom
You'd be nuts to go this route, you are just making a window here user258651, there's no purpose or benefit of doing it like this when we have the Form class.Karee
Creating a Winform in a service sounds nuts to me. Message only windows are not like standard windows. A message only window has only a message pump--and that's all this guy is looking for. Anything more than that, say like a windows forms class is overkill.Dasha
There is actually a class called NativeWindow (msdn.microsoft.com/en-us/library/…) which allows custom parameters in the call to CreateWindowEx with a custom WndProc.Flyman
+1 I agree with jaws because a dependency on System.Windows.Forms just because you want a message loop is not a good architectural decision, especially when you can do it using introp with Windows APIs... A Form or NativeWindow can address this issue, but the cleaner Win APIs are as easyThanasi
K
3

You have to make a Form, Windows services do not interact with the desktop by default, so you have to set the service to interact with the desktop and installing it can be a bit of a pain. The Form will not be visible though. Microsoft has been deliberately making this harder and harder to do because of security issues.

Karee answered 14/3, 2010 at 22:14 Comment(3)
Interesting suggestion. However, allowing the service to interact with the desktop is not an option. I certainly don't want any windows to be visible. The API itself has no visual components, it's just that they decided to use a message pump to notify callers of asynchronous events. Is it a requirement that the service be allowed to interact with the desktop in order to run a message pump?Winterwinterbottom
The window wouldn't be visible. Yes, it's a requirement.Karee
See connect.microsoft.com/VisualStudio/feedback/details/241133/… it has step by step instructions.Karee

© 2022 - 2024 — McMap. All rights reserved.