そのようなインターフェースはサポートされていません
エラー メッセージがあいまいです。サポートされていないのはインターフェイスであると誰もが想定します。あなたの場合は IHello です。しかし、そうではなく、エラー メッセージではそれが十分に明確にされていません。サポートされていないのはIMarshal インターフェイスです。
COM は、.NET が処理しないプログラミングの詳細を処理し、スレッド化を無視しません。スレッドを正しく処理するのが難しいことはよく知られています。スレッドセーフではないコードがたくさんあります。.NET では、このようなコードをワーカー スレッドで使用できます。また、コードを間違えても問題はありません。通常、診断が非常に困難なバグが発生します。COM の設計者は当初、スレッド化を正しく行うのは難しすぎて、賢明な人が対処する必要があると考えていました。とにかくワーカースレッドセーフでスレッドセーフではないコードを使用するようにインフラストラクチャに組み込まれています。これは非常にうまく機能し、典型的なスレッド化の問題の 95% を処理します。ただし、最後の 5% はかなり大きな頭痛の種になる傾向があります。このように。
あなたのような COM コンポーネントは、レジストリ内のスレッドから安全に使用できるかどうかを公開できます。レジストリ値の名前は「ThreadingModel」です。非常に一般的な値であり、欠落している場合のデフォルトでもあるのは "Apartment" です。アパートメントを説明することは、この回答の範囲を少し超えています。実際には、「私はスレッドセーフではない」ことを意味します。COM インフラストラクチャは、オブジェクトに対するすべての呼び出しが、オブジェクトを作成した同じスレッドから行われることを保証するため、スレッド セーフが保証されます。
ただし、それにはいくつかの魔法が必要です。あるスレッドから特定の他のスレッドへの呼び出しをマーシャリングすることは、非常に簡単なことではありません。.NET では、Dispatcher.BeginInvoke や Control.BeginInvoke などのメソッドを使用してシンプルに見えますが、99% が水の中にあるコードのかなり大きな氷山が隠されています。そして、COM はこれを行うのに苦労しています。実装を容易にする .NET 機能がありません。また、リフレクションを直接サポートしていません。
1 つには、呼び出しを行うことができるように、ターゲット スレッドにスタック フレームを構築することが必要です。これには、メソッドの引数がどのように見えるかを知る必要があります。COM はこれについて助けを必要としています。リフレクションに依存できないため、COM はそれらがどのように見えるかを知りません。必要なのは、プロキシとスタブと呼ばれる 2 つのコードです。プロキシは、引数がどのように見えるかを認識しており、メソッドの引数を RPC パケットにシリアル化します。このコードは、元のインターフェイスとまったく同じように見えるダミー インターフェイスを使用して COM によって自動的に呼び出されますが、すべてのメソッドがプロキシ呼び出しを行います。ターゲット スレッドでは、スタブ コードが RPC パケットを受信し、スタック フレームを構築して呼び出しを行います。
これは .NET の用語ではよく知られているように聞こえるかもしれませんが、これはまさに .NET Remoting と WCF が機能する方法です。ただし、Reflection のおかげで、.NET はプロキシとスタブを自動的に作成できます。COM では、ユーザーが作成する必要があります。これには 2 つの基本的な方法があります。一般的な方法は、IDL と呼ばれる言語で COM インターフェイスを記述し、それを midl.exe ツールでコンパイルすることです。これにより、IDL のインターフェイス記述からプロキシとスタブ コードを自動生成できます。または、COM サーバーがそれ自体をオートメーション サブセットに制限し、タイプ ライブラリを生成できる場合に利用できる簡単な方法があります。その場合、Windows に組み込まれているプロキシ/スタブの実装を使用できます。これは、タイプ ライブラリを使用して、引数がどのように見えるかを把握します。これは、リフレクションによく似ています。
したがって、例外メッセージが実際に意味することは、COM が呼び出しをマーシャリングする方法を見つけられないということです。レジストリを調べましたが、プロキシ/スタブのレジストリ キーが見つかりませんでした。次に、COM オブジェクトに「自分自身をマーシャリングする方法を知っていますか?」と尋ねました。IMarshal を照会することによって。答えはノーでした!これで終わりで、解釈するのがかなり難しい例外メッセージが残りました。エラー報告は、COM のアキレス腱です。
次に、COM が COM サーバーへの呼び出しをマーシャリングする必要があると判断した理由に焦点を当てる必要があります。これは、予想外のことです。COM オブジェクトへの呼び出しを行うスレッドの基本的な要件の 1 つは、マーシャリング呼び出しに対して提供するサポートの種類を COM に通知する必要があることです。これは、スタック フレームの構築以外に行うのが難しい 2 番目のことです。呼び出しは、COM オブジェクトを作成した特定のスレッドで行う必要があります。スレッドを実装するコードはこれを可能にする必要があり、それは簡単なことではありません。ソフトウェア工学における一般的な問題である一般的な生産者/消費者問題を解決する必要があります。ここで、「プロデューサー」は呼び出しを行ったスレッドであり、「コンシューマー」はオブジェクトを作成したスレッドです。
したがって、スレッドが COM に伝えなければならないことは、「生産者/消費者の問題に対する解決策を実装しました。先に進んで、自由に生産してください」ということです。この問題に対する一般的な解決策は、ほとんどの Windows プログラマーによく知られています。それは、GUI スレッドが実装する「メッセージ ループ」です。
COM 呼び出しを行うすべてのスレッドが CoInitializeEx() を呼び出さなければならないので、非常に早い段階で COM に通知します。2 つのオプションのいずれかを指定できます。COINIT_APARTMENTTHREADED (別名 STA) を指定して、スレッドセーフではない COM オブジェクトに安全なホームを提供することを約束できます。また「アパート」という言葉が出てきました。または、COINIT_MULTITHREADED (別名 MTA) を指定することもできます。これは基本的に、 COM を支援するために何もせず、COM インフラストラクチャに任せて整理することを意味します。
.NET プログラムは CoInitializeEx() を直接呼び出すのではなく、CLR が呼び出しを行います。また、スレッドが STA か MTA かを知る必要もあります。[STAThread] または [MTAThread] のいずれかを指定して、プログラムのメイン スレッドの Main() メソッドの属性を使用してこれを行います。MTA がデフォルトであり、スレッドプール スレッドのデフォルトかつ唯一のオプションでもあります。または、独自のスレッドを作成するときに、Thread.SetApartmentState() を呼び出して指定します。
MTA とスレッド セーフではない COM オブジェクトの組み合わせ、つまり、「COM を支援するために何もしない」というシナリオは、ここでの問題の一部です。COM にオブジェクトに安全なホームを与えるように強制します。COM インフラストラクチャは、新しいスレッドSTA スレッドを自動的に作成します。支援をオプトアウトしたため、オブジェクトの呼び出しがスレッドセーフであることを保証する他の方法はありません。したがって、オブジェクトに対して行う呼び出しはすべてマーシャリングされます。これはかなり非効率的です。独自の STA スレッドを作成すると、マーシャリング コストが回避されます。しかし、最も重要なのは、COM が呼び出しを行うためにプロキシとスタブを必要とすることです。あなたはそれらを実装していないので、それはカブームです。
これはおそらく CoInitialize() を呼び出すため、C++ クライアント コードで機能しました。STAを選択します。また、vb.net ランタイム サポートは言語に典型的な STA を自動的に選択するため、VB.NET コードで機能しました。これは、プログラマーが成功の穴に落ちるのを助けるために多くのことを自動的に行います。
しかし、それは C# の方法ではありません。自動的に行われることはほとんどありません。Main() メソッドには [STAThread] 属性がなく、デフォルトで MTA になっているため、kaboom が発生しました。
ただし、これは実際には技術的に正しい解決策ではないことに注意してください。STA を約束するときは、その約束も果たさなければなりません。これは、生産者/消費者の問題を解決することを意味します。これには、メッセージ ループ Application.Run() を .NET でポンピングする必要があります。あなたはしませんでした。
その約束を破ると、不快な結果が生じる可能性があります。COM はあなたの約束に依存し、必要に応じて呼び出しをマーシャリングしようとし、それが機能することを期待します。GetMessage() を呼び出していないため、スレッドで呼び出しがディスパッチされません。あなたは消費していません。これはデバッガーですぐに確認できます。スレッドはデッドロックします。、呼び出しは決して完了しません。アパートメント スレッドの COM サーバーは、多くの場合、STA スレッドがメッセージ ループをポンピングし、それを使用して、通常はワーカー スレッドから PostMessage() を呼び出して独自のスレッド間マーシャリングを実装することを容易に想定します。WebBrowser コントロールは良い例です。PostMessage() メッセージがどこにも行かないことの副作用は、通常、コンポーネントがイベントを発生させないか、義務を実行しないことです。たとえば、WebBrowser の場合、DocumentCompleted イベントを取得することはありません。
COM サーバーはこれらの仮定を行わず、それ以外の場合はワーカー スレッドで呼び出しを行わないように思えます。または、C++ または VB.NET クライアント コードで誤動作していることに気付いたでしょう。これはいつでもバイトできる危険な仮定ですが、うまくいく可能性があります。