Windows 10 で .NET 4.5.2 を使用して、C# 内から参照された dll としてサード パーティの SDK を使用してきました。IDE は dll に関する相互運用機能を作成し、適切な名前空間、インターフェイス、列挙型などを確認できます私が使用している SDK は Blackmagic ATEM Television Studio 用ですが、直接関係はないと思います。
SDK は、接続されたハードウェアを検索/検出するために使用できるオブジェクトを提供し、オブジェクトのインスタンスを返します。これは、基になる COM オブジェクトのラッパー (RCW) のように見えます。
検出コードは次のようになります。
string address = "192.168.1.240";
_BMDSwitcherConnectToFailure failureReason = 0;
IBMDSwitcher switcher = null;
var discovery = new CBMDSwitcherDiscovery();
discovery.ConnectTo(address, out switcher, out failureReason);
STA アパートメントである WPF UI スレッドでこのコードを実行すると、問題なく動作します。オブジェクトをエラーなく使用できます。ただし、新しいスレッド (STA または MTA) から結果のスイッチャー オブジェクトにアクセスしようとすると、次のようなエラーが表示されます。
タイプ「System.__ComObject」の COM オブジェクトをインターフェイス タイプ「BMDSwitcherAPI.IBMDSwitcherMixEffectBlock」にキャストできません。IID '{11974D55-45E0-49D8-AE06-EEF4D5F81DF6}' を持つインターフェイスの COM コンポーネントでの QueryInterface 呼び出しが次のエラーのために失敗したため、この操作は失敗しました: サポートされているそのようなインターフェイスはありません (HRESULT からの例外: 0x80004002 (E_NOINTERFACE)) .
MikeJ と同様の質問に対する彼の回答から引用します。
この厄介な例外は、COM マーシャリングと呼ばれる概念のために発生します。問題の本質は、任意のスレッドから COM オブジェクトを使用するために、スレッドが COM オブジェクトを記述する型情報にアクセスできる必要があるという事実にあります。
この時点で、戦略を少し変更して、MTA スレッド内から検出ロジックを呼び出してみることにしました。このような:
private IBMDSwitcher _switcher = null;
public void Connect()
{
Task.Run(() =>
{
string address = "192.168.1.240";
IBMDSwitcherDiscovery discovery = new CBMDSwitcherDiscovery();
_BMDSwitcherConnectToFailure failureReason = 0;
discovery.ConnectTo(address, out _switcher, out failureReason);
});
}
これは機能するようですが、コストがかかります。
トレードオフは、バックグラウンド (MTA) スレッドからスイッチャーオブジェクトを問題なく操作できるようになったことですが、約 15 分間実行すると、アプリケーション全体が単純にクラッシュします。誰かがそれを言う前に、私はこれがスレッドセーフでないことを知っています。複数の MTA スレッドがスイッチャーオブジェクトを使用しようとする前に、何らかの同期ロジックが必要であることを認識しています。現時点では、上記のコードを実行してアプリケーションを 15 分間アイドル状態にすると、毎回クラッシュします。
そのため、一方では安定したアプリを使用していますが、UI スレッドからしかライブラリを使用できず、他方では不安定なアプリを使用していますが、任意の (MTA) バックグラウンド スレッドからライブラリを使用できます。
私のアプリの性質上、複数のバックグラウンド スレッドからライブラリを使用できることが望ましいでしょう。たとえば、1 つのスレッドがグラフィックスを生成してデバイスにアップロードし、別のスレッドがビデオ入力の切り替えを管理し、3 つ目のスレッドがオーディオ入力を管理し、UI スレッドが ID-10T ボンク ダイアログを処理しているとします。 UI スレッドから実行することもできますが、できれば避けたいと思います。
それで、UI(STA)スレッドでスイッチャーオブジェクトを発見して作成する方法はありますか(理論的には15分で爆発するのを避けます)、タイプ情報をバックグラウンド(MTA)スレッドにアクセスできるようにして、バックグラウンド(MTA)スレッドも使用できるようにしますスイッチャー?_ または、バックグラウンド (MTA) スレッドでスイッチャーオブジェクトを検出/作成しても安全ですが、その 15 分間の爆発を避けるために特別な方法でそれを「マーク」する必要がありますか?
編集:
Hans Passant のコメントを読み、彼が提供したリンクを調査した後、参照されている STAThread クラスを WPF で使用するために適合させました。しかし、私は SynchronizationContext と Dispatcher について少し曖昧です。以下にリストされている私のコードは、「長時間実行」操作中に UI をブロックすることなく、COM オブジェクトの「周り」のラッパーとして安全に使用できますか? 私の使用例は、いくつかのデータを準備する Task() を実行してから、いくつかのコードを呼び出して COM オブジェクトと対話し、操作が完了すると、実行中のタスクでコード フローが再開され、次の一連の操作を実行することです。 .
public class STAThread
{
private Thread thread;
private SynchronizationContext ctx;
private ManualResetEvent mre;
public STAThread()
{
using (mre = new ManualResetEvent(false))
{
thread = new Thread(() =>
{
ctx = new SynchronizationContext();
mre.Set();
Dispatcher.Run();
});
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
mre.WaitOne();
}
}
public void BeginInvoke(Delegate dlg, params Object[] args)
{
if (ctx == null) throw new ObjectDisposedException("STAThread");
ctx.Post((_) => dlg.DynamicInvoke(args), null);
}
public object Invoke(Delegate dlg, params Object[] args)
{
if (ctx == null) throw new ObjectDisposedException("STAThread");
object result = null;
ctx.Send((_) => result = dlg.DynamicInvoke(args), null);
return result;
}
}