web-dev-qa-db-ja.com

.NETWindowsサービスのメッセージポンプ

キオスクアプリケーションのすべての外部ハードウェアI/Oを処理するC#で記述されたWindowsサービスがあります。新しいデバイスの1つは、ネイティブDLLにAPIが付属するUSB​​デバイスです。適切なP/Invokeラッパークラスを作成しました。ただし、このAPIはメッセージポンプを使用して非同期イベントを発生させるため、WindowsアプリケーションへのHWndを使用して初期化する必要があります。

Windowsメッセージポンプに依存しないAPIを提供するようにハードウェアの製造元にリクエストする以外に、このAPIに渡すことができるWindowsサービスの新しいスレッドでメッセージポンプを手動でインスタンス化する方法はありますか? ?実際に完全なApplicationクラスを作成する必要がありますか、それともメッセージポンプをカプセル化する低レベルの.NETクラスがありますか?

25
Pickles

あなたの提案をありがとう。リチャード&オーバースラック、コメントで提供したリンクは非常に役に立ちました。また、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();
    }
}

これを機能させるには、いくつかのハードルを克服する必要がありました。 1つは、Application.Runを実行するのと同じスレッドでフォームを作成する必要があることです。また、同じスレッドからのみHandleプロパティにアクセスできるため、そのスレッドでもDLLを初期化するのが最も簡単であることがわかりました。私が知っている限りでは、から初期化されることが期待されています。とにかくGUIスレッド。

また、私の実装では、MessagePumpManagerクラスはシングルトンインスタンスであるため、デバイスクラスのすべてのインスタンスに対して1つのメッセージポンプのみが実行されます。コンストラクターでスレッドを開始する場合は、シングルトンインスタンスを本当にレイジー初期化するようにしてください。静的コンテキスト(private static MessagePumpManager instance = new MessagePumpManager();など)からスレッドを開始する場合、ランタイムは新しく作成されたスレッドにコンテキストスイッチすることはなく、メッセージポンプの開始を待つ間デッドロックが発生します。

40
Pickles

フォームを作成する必要があります。Windowsサービスはデフォルトではデスクトップと対話しないため、デスクトップと対話するようにサービスを設定する必要があり、インストールは少し面倒な場合があります。ただし、フォームは表示されません。マイクロソフトは、セキュリティの問題のために、これを意図的にますます困難にしています。

CreateWindowExの呼び出しでHWND_MESSAGEパラメーターで示されるメッセージのみのウィンドウを作成するだけです。確かに、これはCコードですが、これらの構造体を簡単に作成して、C#で P/Invoke 呼び出しを行うことができます。

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);
1
jaws