WPFxUPnP その10

では、OSに認識されるまで行きましょう!
UPnPはCOMから呼び出され、動作するというのは、いままでの流れで、ご存じかと。
そこで、COMのセキュリティを設定して、動くようにする必要があります。
COMセキュリティを設定するには、CoInitializeSecurityを用います。
C#からの呼び出しは、こんな感じで定義して呼び出します。

#region CoInitializeSecurity関連
enum RpcAuthnLevel
{
    Default = 0,
    None = 1,
    Connect = 2,
    Call = 3,
    Pkt = 4,
    PktIntegrity = 5,
    PktPrivacy = 6
}
 
enum RpcImpLevel
{
    Default = 0,
    Anonymous = 1,
    Identify = 2,
    Impersonate = 3,
    Delegate = 4
}
 
enum EoAuthnCap
{
    None = 0x0000,
    MutualAuth = 0x0001,
    SecureRefs = 0x0002,
    AccessControl = 0x0004,
    AppID = 0x0008,
    Dynamic = 0x0010,
    StaticCloaking = 0x0020,
    DynamicCloaking = 0x0040,
    AnyAuthority = 0x0080,
    MakeFullSIC = 0x0100,
    RequireFullSIC = 0x0200,
    AutoImpersonate = 0x0400,
    Default = 0x0800,
    DisableAAA = 0x1000,
    NoCustomMarshal = 0x2000
}
 
[DllImport("ole32.dll", PreserveSig = false)]
private static extern void CoInitializeSecurity(IntPtr pVoid,
                                                int cAuthSvc,
                                                IntPtr asAuthSvc,
                                                IntPtr pReserved1,
                                                RpcAuthnLevel dwAuthlevel,
                                                RpcImpLevel dwImpLevel,
                                                IntPtr pAuthList,
                                                EoAuthnCap dwCapabilities,
                                                IntPtr pReserved3);
#endregion

// COMセキュリティ設定
CoInitializeSecurity(IntPtr.Zero,
                        -1,
                        IntPtr.Zero,
                        IntPtr.Zero,
                        RpcAuthnLevel.None,
                        RpcImpLevel.Impersonate,
                        IntPtr.Zero,
                        EoAuthnCap.None,
                        IntPtr.Zero);

ここで、1点問題が!
UPnPはMTAで動作します。
しかし!WPFはSTAでしか動作しません。
さて、どうしますか・・・
(STA、MTAってなに?って人は、がんばって調べてw)

実は、ちょっとしたトリックを用いて解決します。
まずは、WPFのアプリケーションをMTAで起動するようにします。

え?WPFコンパイラがMain作って、上書きできない?
そうなんです。
そこで、Mainを生成するApp.xamlをいじって、Mainを生成されないようにします。
Mainを生成すApp.xamlのプロパティをVisualStudioで表示します。
プロパティの設定項目で、「ビルドアクション」の設定値である"ApplicationDefinition"を"Page"にして、
自動でMainを生成されないようにします。

次に、C#のFormアプリケーションのようにMainメソッドを作成します。
ただし、Formと違うのは、MTAスレッドで起動するようにします。

[MTAThread]
public static void Main(string[] args)
{
}

すると、MainはMTAで起動されます。
ここで、UPnPで必要な初期化処理を行います。

次は、GUIWPFの初期化です。
WPFはSTAで起動させるには、STAのスレッドを生成して、そこで初期化を行えばいいのです!
こんな感じ。

[MTAThread]
public static void Main(string[] args)
{
    // GUIをSTAで起動する
    Thread uiThread = new Thread(new ThreadStart(() =>
        {
            App app = new App();
            app.InitializeComponent();
            app.Run();
        }));
    uiThread.SetApartmentState(ApartmentState.STA);

    uiThread.Start();

    // UIの終了待ち
    uiThread.Join();
}

このようにすることで、MainはMTA、GUIWPFはSTAで動作するようになります。

これを用いて、UPnPバイスの初期化を行います。

public static DimmableLightDevice Light = new DimmableLightDevice();

[MTAThread]
public static void Main(string[] args)
{
    try
    {
        // COMセキュリティ設定
        CoInitializeSecurity(IntPtr.Zero,
                                -1,
                                IntPtr.Zero,
                                IntPtr.Zero,
                                RpcAuthnLevel.None,
                                RpcImpLevel.Impersonate,
                                IntPtr.Zero,
                                EoAuthnCap.None,
                                IntPtr.Zero);

        // UPnPデバイス登録
        Light.Register();

        // GUIをSTAで起動する
        Thread uiThread = new Thread(new ThreadStart(() =>
            {
                App app = new App();
                app.InitializeComponent();
                app.Run();
            }));
        uiThread.SetApartmentState(ApartmentState.STA);

        uiThread.Start();

        // UIの終了待ち
        uiThread.Join();
    }
    catch (Exception ex)
    {
        // エラー
        MessageBox.Show(ex.Message, "エラー", MessageBoxButton.OK, MessageBoxImage.Error);
    }
    finally
    {
        // UPnPデバイス登録解除
        Light.UnRegister();
    }
}

これで、実行すると、ネットワークに表示されるようになります。