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で必要な初期化処理を行います。
次は、GUIのWPFの初期化です。
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、GUIのWPFはSTAで動作するようになります。
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(); } }
これで、実行すると、ネットワークに表示されるようになります。