.NET Windows 服务中的消息泵

发布于 2024-08-25 06:45:25 字数 313 浏览 10 评论 0原文

我有一个用 C# 编写的 Windows 服务,用于处理信息亭应用程序的所有外部硬件 I/O。我们的新设备之一是 USB 设备,它带有本机 DLL 中的 API。我创建了一个正确的 P/Invoke 包装类。但是,此 API 必须使用 Windows 应用程序的 HWnd 进行初始化,因为它使用消息泵来引发异步事件。

除了向硬件制造商请求为我们提供一个不依赖于 Windows 消息泵的 API 之外,还有什么方法可以在我的 Windows 服务的新线程中手动实例化消息泵,并将其传递给该 API ?我实际上是否必须创建一个完整的应用程序类,或者是否有一个封装消息泵的较低级别的 .NET 类?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

情何以堪。 2024-09-01 06:45:25

感谢大家的建议。理查德&过度松弛,您在评论中提供的链接非常有帮助。另外,我不必允许服务与桌面交互即可使用 Application.Run 手动启动消息泵。显然,如果您希望 Windows 自动为您启动消息泵,您只需要允许该服务与桌面交互即可。

为了给大家提供启发,以下是我最终为这个第 3 方 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();
    }
}

我必须克服一些障碍才能使其正常工作。一是您需要确保在执行 Application.Run 的同一线程上创建表单。您也只能从同一个线程访问 Handle 属性,因此我发现在该线程上简单地初始化 DLL 也是最简单的。据我所知,无论如何它都期望从 GUI 线程初始化。

另外,在我的实现中,MessagePumpManager 类是一个单例实例,因此只有一个消息泵为我的设备类的所有实例运行。如果在构造函数中启动线程,请确保真正延迟初始化单例实例。如果从静态上下文(例如 private static MessagePumpManager instance = new MessagePumpManager();)启动线程,运行时将永远不会上下文切换到新创建的线程,并且在等待消息泵启动时将出现死锁。

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.

生死何惧 2024-09-01 06:45:25

你必须制作一个Form,Windows服务默认不与桌面交互,所以你必须设置服务与桌面交互,安装它可能有点痛苦。但该表格将不可见。由于安全问题,微软故意让这件事变得越来越难。

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.

往日情怀 2024-09-01 06:45:25

只需创建一个仅包含消息的窗口,由调用 CreateWindowEx 中的 HWND_MESSAGE 参数表示。当然,这是 C 代码,但您可以轻松地创建这些结构和 P/Invoke 调用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);

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文