従来の VB6 COM+ DLL がネイティブ Win32 DLL を呼び出す - STA でのスレッドの問題?

StackOverflow https://stackoverflow.com/questions/1057524

質問

一見すると MT の問題のように見えますが、COM+ で使用される STA モデルを詳細に理解しようとしています。

実際には、C++ で書かれたネイティブ (つまり、COM ではない) Win32 DLL を呼び出す、VB6 で書かれたレガシー COM+ コンポーネントがあります。

断続的な (そしてテストでは再現不可能な) 問題が発生したため、何が起こっているのかを調べるためにデバッグ コードを追加したところ、問題が発生したときにログ メッセージがファイルにインターリーブされていることがわかりました。つまり、DLL が2 つのスレッドによって同時に呼び出されていました。

現在、ログは _getpid() と GetCurrentThreadId() に基づいてスレッドごとのファイルに記録されるため、C++ DLL 内のコードが呼び出されるときに、同じスレッド上で同時に 2 回呼び出されているようです。STA についての私の理解では、COM がオブジェクトの個々のインスタンスを単一のスレッドにマーシャリングし、任意に実行を中断および再開するため、これが当てはまる可能性があることを示唆しています。

残念ながら、ここからどこへ行けばよいのかわかりません。これが STA DLL であることを COM に伝えるために、DllMain() で CoInitialiseEx() を呼び出す必要があると読んでいますが、他の場所では、これは COM DLL でのみ有効であり、ネイティブ DLL では効果がないと述べています。他の唯一のオプションは、DLL の一部をクリティカル セクションとしてラップして、アクセスをシリアル化することです (パフォーマンスに影響を与えるものはすべて考慮します)。

DLLを再加工してみることもできますが、共有状態やグローバル変数はありません。すべてがローカル変数内にあるため、理論的には各呼び出しは独自のスタックを取得する必要がありますが、STAモデルが基本的にこれに何らかの奇妙な影響を与えているのではないかと思っていますそして、別の呼び出しと同じエントリ ポイントで、すでにロードされている DLL に再入力するだけです。残念ながら、この理論を証明またはテストする方法はわかりません。

質問は基本的に次のとおりです。

  1. STA COM+コンポーネントがネイティブDLLを呼び出すと、STAモデルには、アクティブな「スレッド」が吊り下げられ、DLLコールの途中で別の「スレッド」に渡​​されるのを防ぐために何もありませんか?
  2. CoInitialiseEx() はこれを解決する正しい方法ですか?
  3. (1) も (2) も「適切な」仮定ではない場合、何が起こっているのでしょうか?
役に立ちましたか?

解決

アパートメント スレッド COM サーバーでは、COM クラスの各インスタンスは単一のスレッドによってアクセスされることが保証されます。これはつまり インスタンス スレッドセーフです。ただし、異なるスレッドを使用して、多くのインスタンスを同時に作成できます。さて、COM サーバーに関する限り、ネイティブ DLL は特別なことを何もする必要はありません。すべての実行可能ファイルで使用される kernel32.dll について考えてみましょう。COM サーバーで使用されると、COM が初期化されますか?

DLL の観点からは、異なるインスタンスが同時に呼び出すことができるため、スレッドセーフであることを確認する必要があります。この場合、STA はあなたを保護しません。グローバル変数を使用していないとおっしゃっているので、問題は他の場所にあり、COM のものを指していると思われる状況でたまたま発生しているとしか考えられません。単純な古い C++ メモリの問題が発生していませんか?

他のヒント

あなたの問題は、呼び出された DLL の奥深くで、別のアパートメント (同じプロセス内の別のスレッド、MTA 内のオブジェクト、または完全に別のプロセス) へのアウトバウンド COM 呼び出しが行われたことではないかと思います。COM では、発信呼び出しの結果を待機している STA スレッドが別の着信呼び出しを受信し、それを再帰的に処理することができます。これは、同じオブジェクト間での継続的な会話のみを目的としています。A が B にコールし、B が A にコールバックし、A が再び B にコールバックします。ただし、インターフェイス ポインタを複数のクライアントに渡している場合、またはクライアントがインターフェイス ポインタを別のクライアントに共有している場合は、他のオブジェクトからの呼び出しを受け取ることができます。一般に、シングルスレッド オブジェクトへのインターフェイス ポインタを複数のクライアント スレッドに渡すのは、お互いに待機するだけで済むため、お勧めできません。スレッドごとに 1 つのワーカー オブジェクトを作成します。

COM は、どのスレッドでも自由に実行を一時停止したり再開したりすることはできません。STA スレッド上の新しい着信呼び出しは、メッセージ ポンプを介してのみ到着できます。応答を待って「ブロック」されている場合、STA スレッドは実際にメッセージをポンピングし、メッセージを処理する必要があるかどうかをメッセージ フィルター (IMessageFilter を参照) でチェックします。ただし、メッセージ ハンドラーは新しい発信呼び出しを行ってはなりません。発信すると、COM は RPC_E_CANTCALLOUT_INNEXTERNALCALL エラー (「メッセージ フィルター内で呼び出しを行うのは不正です。」) を返します。

ネイティブ DLL 内のどこかにメッセージ ポンプ (GetMessage/DispatchMessage) がある場合にも、同様の問題が発生する可能性があります。インターフェイスプロシージャ内のVBのDoEventsで問題が発生しました。

CoInitializeEx はスレッドの作成者のみが呼び出す必要があります。メッセージ ポンピング動作がどのようなものになるかはスレッドの作成者だけが知っているからです。DllMain で呼び出そうとすると、単純に失敗する可能性があります。ネイティブ DLL は COM 呼び出しに応答して呼び出されるため、最終的に呼び出し元は呼び出しを行うためにスレッド上で CoInitializeEx を呼び出している必要があります。新しく作成されたスレッドに対して DLL_THREAD_ATTACH 通知でこれを実行すると、表面的には機能する可能性がありますが、ポンピングする必要があるときに COM がブロックされた場合、またはその逆の場合にプログラムが誤動作する可能性があります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top