7

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

事実上、VB6 で記述された従来の COM+ コンポーネントがあり、C++ で記述されたネイティブ (つまり、COM ではない) Win32 DLL を呼び出します。

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

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

残念ながら、ここからどこへ行くべきかわかりません。DllMain() で CoInitialiseEx() を呼び出して、これが STA DLL であることを COM に通知する必要があることを読んでいますが、他の場所では、これは COM DLL に対してのみ有効であり、ネイティブ DLL では効果がないと言っています。他の唯一のオプションは、DLL の一部をクリティカル セクションとしてラップして、アクセスをシリアル化することです (あごにかかるパフォーマンス ヒットはすべて受け入れます)。

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

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

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

2 に答える 2

1

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

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

于 2009-06-29T11:04:22.200 に答える
0

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

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

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

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

于 2010-03-02T19:05:01.217 に答える