27

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?

John Saunders
  • 157,405
  • 24
  • 229
  • 388
Pickles
  • 1,082
  • 2
  • 10
  • 18
  • 4
    Check out this thread, it may prove helpful: http://connect.microsoft.com/VisualStudio/feedback/details/241133/detecting-a-wm-timechange-event-in-a-net-windows-service – overslacked Mar 14 '10 at 22:34
  • @overslacked, it does indeed. It says exactly how to do it. – DoctorFoo Mar 15 '10 at 05:36
  • @overslacked 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! – Sabuncu Sep 03 '15 at 17:57
  • @Sabuncu - 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. – overslacked Sep 03 '15 at 18:25
  • @overslacked I have no problem w/ the accepted answer. I am just curious about the content of the Connect link. – Sabuncu Sep 03 '15 at 18:27
  • 2
    @Sabuncu - I see! In that case, it looks like archive.org has a copy from 2014-07-06 - https://web.archive.org/web/20140706130218/http://connect.microsoft.com/VisualStudio/feedback/details/241133/detecting-a-wm-timechange-event-in-a-net-windows-service – overslacked Sep 03 '15 at 18:31
  • @overslacked A huge THANK YOU! This completes the puzzle. – Sabuncu Sep 03 '15 at 18:36

3 Answers3

42

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.

Pickles
  • 1,082
  • 2
  • 10
  • 18
  • 3
    Thanks for taking the time to post a solution to your problem - you just saved me hours of work. – Basic Nov 18 '10 at 18:29
  • 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... – JobaDiniz Dec 01 '15 at 12:25
2

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.

DoctorFoo
  • 10,061
  • 3
  • 39
  • 61
  • 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? – Pickles Mar 15 '10 at 07:40
  • The window wouldn't be visible. Yes, it's a requirement. – DoctorFoo Mar 15 '10 at 13:17
  • See https://connect.microsoft.com/VisualStudio/feedback/details/241133/detecting-a-wm-timechange-event-in-a-net-windows-service?wa=wsignin1.0 it has step by step instructions. – DoctorFoo Mar 15 '10 at 13:18
1

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);
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
jaws
  • 1,832
  • 4
  • 19
  • 26
  • 1
    Certainly don't need to use P/Invoke calls to make a window. – DoctorFoo Mar 14 '10 at 22:18
  • I don't think you can make a HWND_MESSAGE window without a call to CreateWindowEx. – jaws Mar 14 '10 at 22:22
  • 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. – Pickles Mar 15 '10 at 07:42
  • 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. – DoctorFoo Mar 15 '10 at 14:28
  • 2
    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. – jaws Mar 15 '10 at 14:49
  • 2
    There is actually a class called `NativeWindow` (http://msdn.microsoft.com/en-us/library/System.Windows.Forms.NativeWindow(v=vs.110).aspx) which allows custom parameters in the call to `CreateWindowEx` with a custom `WndProc`. – Mitch Apr 10 '14 at 21:36
  • +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 easy – Mzn Apr 11 '15 at 18:44