Насос сообщений в .NET Windows service - PullRequest
24 голосов
/ 15 марта 2010

У меня есть служба Windows, написанная на C #, которая обрабатывает все наши внешние аппаратные операции ввода-вывода для приложения киоска. Одним из наших новых устройств является USB-устройство, которое поставляется с API в собственной DLL. У меня есть правильный класс оболочки P / Invoke создан. Однако этот API должен быть инициализирован с помощью HWnd для приложения Windows, поскольку он использует насос сообщений для генерации асинхронных событий.

Помимо запроса производителя оборудования о предоставлении нам API, который не зависит от обработчика сообщений Windows, есть ли способ вручную создать обработчик сообщений в новом потоке в моей службе Windows, который я могу передать в этот API? Нужно ли создавать полноценный класс Application или существует класс .NET более низкого уровня, который инкапсулирует насос сообщений?

Ответы [ 3 ]

39 голосов
/ 16 марта 2010

Спасибо всем за ваши предложения. Richard & overslacked, ссылка, которую вы предоставили в комментариях, была очень полезной. Кроме того, мне не нужно было разрешать службе взаимодействовать с рабочим столом, чтобы вручную запустить насос сообщений с помощью Application.Run. Очевидно, вам нужно только разрешить службе взаимодействовать с рабочим столом, если вы хотите, чтобы Windows автоматически запускала рассылку сообщений для вас.

Для наглядности, вот что я сделал, чтобы вручную запустить насос сообщений для этого стороннего 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 является экземпляром Singleton, поэтому для всех экземпляров моего класса устройств работает только один насос сообщений. Убедитесь, что вы действительно лениво инициализируете свой экземпляр singleton, если запускаете поток в своем конструкторе. Если вы запускаете поток из статического контекста (такого как частный статический экземпляр MessagePumpManager = new MessagePumpManager ();), среда выполнения никогда не переключится в контекст во вновь созданный поток, и вы будете в тупике, ожидая запуска насоса сообщений. *

2 голосов
/ 15 марта 2010

Вы должны создать форму, службы Windows по умолчанию не взаимодействуют с рабочим столом, поэтому вам нужно настроить службу для взаимодействия с рабочим столом, и ее установка может быть немного трудной. Форма не будет видна, хотя. Microsoft намеренно делает это все труднее и труднее из-за проблем с безопасностью.

1 голос
/ 15 марта 2010

Просто создайте окно только для сообщений, обозначенное параметром HWND_MESSAGE в вызове CreateWindowEx. Конечно, это код 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);
...