4

この質問で説明されている問題があります: Unable to cast COM object of type exception で、エラーとして現れます:

タイプ 'System.__ComObject' の COM オブジェクトをインターフェイス タイプ 'IMyInterface' にキャストできません。IID '{GUID}' を持つインターフェイスの COM コンポーネントでの QueryInterface 呼び出しが次のエラーのために失敗したため、この操作は失敗しました: そのようなインターフェイスはサポートされていません

私の WPF アプリは、最終的に COM オブジェクトを呼び出す .NET ライブラリを呼び出しています。これは問題なく動作しますが、メイン スレッドで実行され、UI をブロックします。新しいスレッドを生成してそこからライブラリを呼び出すと、そのエラーが発生します。他の質問に関するこれらの解決策はどれも私にとってはうまくいきません。ランタイムが型情報をロードする方法を理解しようとしていますが、スレッド間で共有することはできません。

私は、WPF アプリが STA であることを理解しています。つまり、スレッド間を移動するすべてのオブジェクトが COM マーシャリングされることを知っています。「これは COM オブジェクトであり、その GUID はこれです」という型情報が AppDomain にあるのに、2 番目のスレッドにアクセスできない方法がわかりません。

ロードされた型情報はどこに存在しますか? それはAppDomainにありますか、それともスレッドごとですか? いずれにせよ、スレッドに型情報を共有させるにはどうすればよいですか? どうすればこれを修正できますか?

私はこれを読みました:
http://www.codeproject.com/Articles/9190/Understanding-The-COM-Single-Threaded-Apartment-Pa
とこれ:
http://msdn.microsoft.com/en-us/ library/ms973913.aspx#rfacomwalk_topic10
および COM 相互運用性について説明している他の多くのものを参照してください。

Hans Passant の回答に従って、2 番目の STA スレッド内にライブラリ オブジェクトを作成しています。

        var thread = new Thread(delegate()
        {
            var aeroServer = new AeroServerWrapper(Config.ConnectionString);
            var ct = new CancellationToken();
            aeroServer._server.MessageReceived += ServerMessageReceived;
            aeroServer.Go(@"M:\IT\Public\TestData\file.dat", ct);
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

イベントが発生しない場合、Application.Run() を呼び出してメッセージ キューを開始する必要があるかもしれないことは理解していますが、そこまで到達していません。COM オブジェクトを作成しようとするとすぐにクラッシュします。AeroServerWrapper は別の DLL にあり、これが 2 番目の DLL を呼び出し、最終的に COM オブジェクトのインスタンス化を試みます。

ヒントや記事をいただければ幸いです。これをそのまま解決したいと思います。私のプラン B は、コンソール アプリでライブラリをラップし、UI からコンソール アプリを生成し、名前付きパイプを介してコンソール アプリからステータス メッセージを取得することです。それはうまくいきますが、醜いようです。

4

2 に答える 2

6

別のスレッドから COM インターフェイス メソッドを呼び出しているため、これは正しくありません。COM は、自身がスレッド セーフでないと宣言する COM クラスが、スレッド セーフな方法で呼び出されることを保証します。レジストリの ThreadModel キーはこれを指定します。非常に一般的な値は "Apartment" (または見つからない) で、クラスがスレッドセーフでないことを示します。

そのため、別のスレッドからインターフェイス メソッドを呼び出すと、COM が介入し、オブジェクトが作成されたスレッドへの呼び出しをマーシャリングして、スレッド セーフを確保します。.NET クラスにはまったくない非常に優れた機能です。ただし、インターフェイス メソッドの引数をマーシャリングするときは、COM に助けが必要です。呼び出しが別のスレッドで行われ、メソッド呼び出しの引数値をコピーする必要があるため、必須です。リフレクションは COM 機能ではありません。最初に行うことは、レジストリ、HKCR\Interface\{guid}ProxyStubClsid キーのキー、引数をシリアル化する方法を知っているヘルパー クラスの GUID を調べることです。

明らかに、そのキーがマシンにありません。次に行うことは、COM オブジェクトに IMarshal インターフェイスを要求することです。明らかに、COM サーバーはそれを実装していません。そして、それは E_NOINTERFACE エラーを生成します。適切なエラー メッセージを生成することは、COM の強みではありません。

さて、落書きは壁にあります。使用している COM クラスはスレッド セーフではなく、それをスレッド セーフに呼び出すために必要なすべての配管がありません。通常、プロキシ/スタブを提供するのは非常に簡単ですが、コンポーネントの作成者は気にしませんでした。まったく珍しいことではありません。彼がそうしたとしても、それが役立つというわけではありません。その配管により、メソッドが UI スレッドで実行されることが保証されます。これは、最初に回避しようとしていたことです。

できる唯一の合理的な方法は、独自のスレッドを作成し、その SetApartmentState() メソッドを呼び出して STA に切り替え、そのスレッドでオブジェクトを作成して、クラスがスレッドセーフな方法で使用されるようにすることです。並行性はありませんが、少なくともコードの残りの部分と並行しています。そのスレッドは通常、メッセージ ループ Application.Run() もポンピングする必要があります。ポンピングしなくても済む場合があります。デッドロックやイベントが発生しない場合は、ポンプする必要があることがわかります。サンプル コードは、この投稿にあります。

于 2012-07-13T14:15:12.210 に答える
1

実際の COM インターフェイス タイプのラッパーである .NET インターフェイス タイプ IMyInterface は正常にロードされます。問題は、他のスレッドで何らかの形で「見えない」ということではありません。キャストしようとしているオブジェクトの実際の型は、ご覧のとおり、System.__ComObject であり、とにかくそのインターフェイスを実装していません。キャストおよび型チェック (is/as 演算子) の際には、他の .NET オブジェクトのようには扱われません。.NET ランタイムは、 QueryInterfaceを呼び出して、キャストしようとしているインターフェイスを COM オブジェクトに要求します。COM オブジェクトが正しくマーシャリングしないため、呼び出しは失敗します (または、そうでない場合: 最終結果は失敗です)。InvalidCastException として現れるのは、.

于 2012-07-13T13:37:40.480 に答える