0

CoRegisterClassObject を使用して、com オブジェクトを含む dll をロードする方法をカスタマイズしようとしています。スレッドのアパートメント タイプが com オブジェクトのものと一致しないときに発生していた問題を解決する方法を試しています。基本的な考え方は、coregisterclassobject を使用すると com オブジェクトの作成時にレジストリが無視されるため、STA オブジェクトが STA スレッドで作成されていることを確認する必要があり、MTA オブジェクトについても同様であるということです。これは、常に期待どおりに動作するとは限らない概念実証として作成したサンプルです。

LPSTREAM factory_stream = NULL; //GLOBAL VARIABLE FOR TEST

DWORD __stdcall FactoryThread(LPVOID param)
{
   CoInitialize(NULL);
   //CoInitializeEx(NULL, COINIT_MULTITHREADED);

   cout << GetCurrentThreadId(); //THREAD_ID_2

   CustomClassFactory *factory = new CustomClassFactory();
   factory->AddRef();
   CoMarshalInterThreadInterfaceInStream(IID_IClassFactory, (IClassFactory*)factory, &factory_stream);
   MSG msg;
   while (GetMessage(&msg, NULL, 0, 0)) 
   {
     TranslateMessage(&msg);
     DispatchMessage(&msg);
   }
   factory->Release();
   CoUninitialize();
   return 0;
}

そして、これが私の主な機能の関連部分です。

//CoInitialize(NULL);
CoInitializeEx(NULL, COINIT_MULTITHREADED);

cout << GetCurrentThreadId(); //THREAD_ID_1

HANDLE regThread = CreateThread(NULL, 0, FactoryThread, NULL, 0, NULL);
Sleep(5000); //ensures that the factory is registered

IClassFactory *factory = NULL;
CoGetInterfaceAndReleaseStream(factory_stream, IID_IClassFactory, (void**)&factory);

DWORD regNum = 0;
HRESULT res = CoRegisterClassObject(clsid, factory, CLSCTX_INPROC_SERVER, REGCLS_MULTI_SEPARATE, &regNum);
{
   TestComObjLib::ITestComObjPtr ptr;
   HRESULT hr = ptr.CreateInstance(__uuidof(TestComObjLib::TestComObjCoClass), NULL);
   ptr->OutputOwningThreadId(); //THREAD_ID_3 is just from cout << GetCurrentThreadId()

   TestComObjLib::ITestComObjPtr ptr2;
   HRESULT hr = ptr2.CreateInstance(__uuidof(TestComObjLib::TestComObjCoClass), NULL);
   ptr2->OutputOwningThreadId(); //THREAD_ID_4
}
CoRevokeClassObject(regNum);
CoUninitialize();

CoRegisterClassObject でレジストリを使用するべきではないため、現在の MTA スレッドではなく STA でアパートメント スレッド オブジェクトを手動で作成する必要があり、その逆も同様であるという考えでした。CoRegisterClassObject を使用していない場合、CoGetClassObject は新しいスレッドを生成し、そのスレッドで DllGetClassObject を呼び出すことに気付きました。そのため、クラス ファクトリを STA で作成するだけで、オブジェクトがそこに存在することがわかりました。

私が見ている問題は、上記の例では、スレッド ID が常に期待どおりに表示されるとは限らないことです。FactoryThread がアパートメント スレッドとして初期化され、メイン スレッドがマルチスレッドとして初期化されている場合、期待どおりに THREAD_ID_2 == THREAD_ID_3 == THREAD_ID_4 != THREAD_ID_1 になります (ファクトリはこれらのオブジェクトを作成しており、ファクトリのスレッドに存在できます)。ただし、これらのスレッド モデルが切り替えられると、thread_id_3 == thread_id_4 になりますが、com オブジェクトはスレッド 2 で作成できますが、thread_id_2 および thread_id_1 とは異なります。

これは一貫性がないように思われ、別のスレッドが関与している状況では望ましくない動作を引き起こす可能性があります。レジストリのみに依存し、coregisterclassobject を使用しない場合、STA でフリースレッド オブジェクトを作成すると、そのオブジェクトは、MTA にある com によって生成された別のスレッドで作成されます。も STA にあり、そこでオブジェクトを作成すると、新しいスレッドではなく、com によって生成された最初の MTA スレッドに配置されます (オブジェクトの threadingmodel とスレッドのアパートメント タイプが逆の場合も同様です)。ただし、上記のように coregisterclassobject を使用して独自のファクトリを作成し、オブジェクトがマルチスレッド化されていてスレッドが STA にある場合、これらのマルチスレッド化オブジェクトを作成した新しいスレッドごとに新しい MTA スレッドが生成されます。

4

1 に答える 1

3

マルチスレッド アパートメントでクラス ファクトリを作成すると、複数のスレッドから呼び出すことができます。したがって、「マルチスレッド」という名前です。なぜあなたはそれを驚くべきだと思いますか?

具体的には、COM ランタイムは、MTA へのクロスアパートメント呼び出しを実行するスレッドのプールを維持します。マルチスレッドであることを宣言するオブジェクトは、それらのスレッドのいずれかで呼び出すことができます。

次に、STA にもある 3 番目のスレッドを生成した場合、そこでオブジェクトを作成すると、新しいスレッドではなく、com によって生成された最初の MTA スレッドに配置されます。

この発言はあまり意味がありません。マルチスレッド オブジェクトは特定のスレッドに属していないため、「オブジェクト ... を MTA スレッドに入れる」という意味が明確ではありません。マルチスレッド オブジェクトは、MTA に参加した任意のスレッド (プログラムが明示的に作成したスレッドか、COM ランタイムによって作成されたスレッドか) を作成して呼び出すことができます。このような複数のスレッドによって同時に呼び出される場合があります。

あなたが観察する行動の違いは、この事実によるものです。アパートメント間の呼び出しは、ウィンドウ メッセージの形式で STA スレッドに配信されます。STA スレッドは、 を呼び出して、着信呼び出しを受け入れる準備ができていることを通知しますGetMessage。一方、MTA へのクロス アパートメント コールでは、ウィンドウ メッセージは使用されませんが、文書化されておらず、特定されていないメカニズムが使用されます。このような呼び出しは、COM によって作成されたスレッド プールのスレッドによってのみ提供されます。COM ランタイムは、明示的に作成されたスレッドを単に要求することはできません。これは、特定の時点でそのスレッドが何を行っているかがわからないためです。スレッドが「任意の COM 呼び出しを受け入れて実行する準備ができました」と言うことができる API はありません。実際には、COM のスレッド プールに参加します。

これを念頭に置いて、シナリオを見てみましょう。ケース A: 通常の COM オブジェクトが に登録されておりThreadingModel=Free、カスタム クラス ファクトリにはおかしなことはありません。STA スレッドはCoCreateInstance、そのオブジェクトのCLSID. COM はレジストリから情報を読み取り、オブジェクトがマルチスレッドであることを検出し、MTA スレッド プール内のいずれかのスレッドへの呼び出しをマーシャリングします。これにより、オブジェクトが作成され、インターフェイス ポインターがマーシャリングされます。STA スレッド (同じスレッドまたは別のスレッド) がCoCreateInstance同じ で再度呼び出すとCLSID、プロセスが繰り返され、プールの同じスレッドがそれを処理することがあります。

ところで、オブジェクトを作成したスレッドは、OutputOwningThreadId呼び出しを処理するスレッドと同じである必要はありません。実際、OutputOwningThreadId連続して 2 回呼び出した場合、特に複数のスレッドから同じオブジェクトに対して同時に呼び出した場合、異なるスレッド ID が報告される可能性が高くなります。これは誤解です。MTA には、「所有スレッド」などというものはありません。

ケース B: クラス ファクトリを作成する明示的な をスピンし、何かFactoryThreadを行うのに忙しくなります(メッセージ ポンプをスピンしているという事実は、MTA では無関係です。このスレッドは、COM ランタイムには立ち入り禁止です。私が言ったように、COM は何をしていても魔法のようにそれを中断して、何らかの COM 呼び出しを実行させることはできません。したがって、A の場合と同様に、すべての後続のおよび (不適切な名前の)呼び出しは、COM が保持するスレッド プールの一部のスレッドで実行されますが、.Sleep(INFINITE)CreateInstanceOutputOwningThreadIdFactoryThread

はい、あなたのアプローチでは、基本的に 1 つのスレッドを無駄にしています。これは、レジストリを回避できるという利点のために支払う大きな代償のようには思えません。

于 2013-07-24T20:32:47.293 に答える