2009-06-29 38 views
7

MT問題のような視線を見ていますが、COM +で使用されるSTAモデルを詳しく理解しようとしています。レガシーVB6 COM + DLLがネイティブWin32 DLLを呼び出す - STAのスレッディング問題?

効果的には、VB6で書かれた従来のCOM +コンポーネントがあり、C++で書かれたネイティブ(つまり、COMではない)Win32 DLLを呼び出します。

問題が発生したときにログメッセージがファイル内にインターリーブされていることを確認するために、いくつかの間欠的な問題(テストでは再現できない問題)が発生しました。 DLLが一度に2つのスレッドによって呼び出されていることを暗示しています。

ここで、_getpid()とGetCurrentThreadId()に基づいてスレッドごとのファイルにロギングが行われるため、C++ DLLのコードが呼び出されると同時に同じスレッドで2回呼び出されるようです。私のSTAの理解によれば、COMが1つのスレッド上のオブジェクトの個々のインスタンスを一時停止して実行を再開すると、これが当てはまる可能性があります。

ここではどこから行くのか不安です。私は、これがSTA DLLであることをCOMに伝えるためにDllMain()でCoInitialiseEx()を呼び出す必要があることを読んでいますが、これはCOM DLLに対してのみ有効で、ネイティブDLLには影響しません。他の唯一のオプションは、DLLの一部をクリティカルセクションとしてラップしてアクセスをシリアライズすることです。

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

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

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

>>同じスレッドで同時に2回呼び出されています。 <<今は面白いことですが、「同時に」することはできません。おそらく、このコードは再帰的に自分自身を最高の状態で呼び出すでしょう。 – wqw

答えて

1

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

DLLの観点からは、異なるインスタンスが同時にあなたに電話できるので、スレッドセーフであることを確認する必要があります。 STAはこの場合あなたを保護しません。あなたはグローバル変数を使用していないと言いますから、問題は他の場所にあると仮定することができ、COMの要素を指し示すような状況でのみ発生します。普通のC++メモリの問題がないと確信していますか?

+0

問題は、本稼動システムではロード時に発生するように見えます。 DLLのコードには、ローカル変数のみを使用するグローバル変数はありません。スレッドとプロセスID単位で一意の名前が付けられた出力ログファイルには、中間の状態で同じスレッドによって2回呼び出されていることが示唆される、インターリーブされた出力が含まれています。 他のすべての環境では、コードは完全に正しく実行されます。 – THEMike

+0

1.実際にどのような問題が発生していますか?クラッシュ、ハングアップ、不正行為? 2.直接または間接的にスレッドを作成しているのか、メッセージを送信していますか? 3.問題を特定するとすぐにミニダンプ(またはフルダンプ)を作成してみてください。スタックトレースは、dev envの快適さで問題を特定するのに役立ちます。 – eran

+0

"古くなった古いC++メモリの問題"を修正しました:これは最後にありましたが、malloc()タイプのアリーナではなく、静的として定義されたchar配列を持っています。レイモンド・チェンは、これについてhttp://blogs.msdn.com/oldnewthing/archive/2004/03/08/85901.aspxにブログを書きました。私は井戸として他のいくつかの場所で確認を受けました。静的コードをリファクタリングして解決されるようです。 –

0

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

COMは任意のスレッドで自由に実行を一時停止および再開できません.STAスレッド上の新しい着信コールは、メッセージポンプ経由でのみ受信できます。 「ブロックされた」応答を待っているとき、STAスレッドは実際にメッセージをポンピングし、メッセージフィルタを使用してメッセージを処理する必要があるかどうかを確認します(IMessageFilterを参照)。ただし、メッセージハンドラでは、新しい発信コールを作成しないでください。COMを使用すると、RPC_E_CANTCALLOUT_INEXTERNALCALLエラーが返されます(「メッセージフィルタ内でコールするのは不正です」)。

メッセージポンプ(GetMessage/DispatchMessage)をネイティブDLL内のどこにでも置くことができます。私は、インターフェイスプロシージャでVBのDoEventsに問題がありました。

CoInitializeExは、スレッドの作成者によってのみ呼び出される必要があります。なぜなら、メッセージのポンピング動作が何であるかを知っているからです。 DllMainで呼び出そうとすると、ネイティブDLLがCOM呼び出しに応答して呼び出されるため、呼び出し元が最終的に呼び出すためにCoInitializeExを呼び出している必要があります。新しく作成されたスレッドのDLL_THREAD_ATTACH通知でそれを実行すると、表面的には動作するかもしれませんが、COMがブロックするとブロックするとプログラムが誤動作し、逆もまた同様です。

関連する問題