0

我正在尝试使用 CoRegisterClassObject 来自定义加载包含 com 对象的 dll 的方式。我正在尝试解决当线程的单元类型与 com 对象不匹配时遇到的问题。基本思想是,由于在创建 com 对象时使用 coregisterclassobject 会忽略注册表,因此我需要确保 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 一起使用,我需要在 STA 而不是当前的 MTA 线程中手动创建单元线程对象,反之亦然。我注意到,当不使用 CoRegisterClassObject 时,CoGetClassObject 会生成一个新线程并在该线程中调用 DllGetClassObject,所以我认为只需要在 STA 中创建类工厂,然后对象就会在那里。

我看到的问题是,在上面的示例中,线程 ID 并不总是看起来像我期望的那样。如果 FactoryThread 初始化为单元线程,主线程为多线程,则 THREAD_ID_2 == THREAD_ID_3 == THREAD_ID_4 != THREAD_ID_1 符合预期(工厂正在创建这些对象,它们可以存在于工厂的线程中)。如果切换了这些线程模型,那么 thread_id_3 == thread_id_4,但它们与 thread_id_2 和 thread_id_1 不同,即使可以在线程 2 中创建 com 对象。

这似乎不一致,并且在涉及另一个线程的情况下可能导致不需要的行为。当仅依赖注册表而不使用 coregisterclassobject 时,如果我在 STA 中创建了一个自由线程对象,则该对象将在由 MTA 中的 com 生成的不同线程中创建,然后如果我生成了第三个线程也在 STA 中,在那里创建对象会将其放入第一个 com 生成的 MTA 线程中,而不是新线程中(如果对象的线程模型和线程的单元类型颠倒了,情况也是如此)。但是,如果我要使用 coregisterclassobject 像上面那样创建自己的工厂,并且对象是多线程的,但线程在 STA 中,那么创建这些多线程对象的每个新线程都会产生一个新的 MTA 线程,

4

1 回答 1

3

在多线程单元中创建类工厂时,可以从多个线程调用它。因此称为“多线程”。为什么你会感到惊讶?

具体来说,COM 运行时维护一个线程池,用于执行对 MTA 的跨单元调用。然后可以在任何这些线程上调用任何声明自己为多线程的对象。

然后,如果我在 STA 中生成了第三个线程,则在那里创建对象会将其放入第一个 com 生成的 MTA 线程,而不是新线程

这种说法没有多大意义。多线程对象不属于任何特定线程,因此不清楚您所说的“对象......放在 MTA 线程中”是什么意思。可以在任何加入 MTA 的线程上创建和调用多线程对象(无论是您的程序显式创建的线程,还是由 COM 运行时创建的线程);它可以被几个这样的线程同时调用。

您观察到的行为差异是由于这一事实。跨单元调用以窗口消息的形式传递给 STA 线程。STA 线程通过调用来表示其准备好接受传入呼叫GetMessage。另一方面,对 MTA 的跨单元调用不使用窗口消息,而是使用其他一些未记录和未指定的机制。这样的调用只能由 COM 创建的线程池中的线程提供服务 - COM 运行时不能仅仅征用您明确创建的线程,因为它不知道该线程在任何给定时间正在做什么。没有 API 允许您的线程说“我已准备好接受并执行任意 COM 调用”——实际上是加入 COM 的线程池。

考虑到这一点,让我们看看你的场景。案例 A:你有一个注册的常规 COM 对象ThreadingModel=Free,没有自定义类工厂的有趣业务。STA 线程CoCreateInstance使用该对象的CLSID. COM 从注册表中读取信息,发现对象是多线程的,并将调用编组到其 MTA 线程池中的线程之一,该线程池创建对象并将其接口指针编组回来。如果一个 STA 线程(相同的线程或另一个线程)CoCreateInstance再次使用相同的 调用CLSID,则重复该过程,并且可能恰好发生池中的相同线程处理它。

顺便说一句,创建对象的线程不必是处理OutputOwningThreadId调用的同一线程。事实上,如果你OutputOwningThreadId连续调用两次——特别是如果你同时在多个线程的同一个对象上调用它——它很可能会报告不同的线程 ID。这是用词不当:在 MTA 中,不存在“拥有线程”之类的东西。

案例 B:您旋转FactoryThread创建类工厂的 explicit ,然后忙于做某事(它旋转消息泵的事实与 MTA 无关;它也可以Sleep(INFINITE))。该线程禁止 COM 运行时;正如我所说,COM 不能在它正在做的任何事情中神奇地中断它,并让它执行一些 COM 调用。因此,就像情况 A 一样,所有后续CreateInstance和(名称错误的)OutputOwningThreadId调用都在 COM 维护的线程池中的某些线程上执行,但从不在FactoryThread.

是的,在您的方法中,您基本上是在浪费一个线程。这似乎不是为了避免注册而付出的巨大代价。

于 2013-07-24T20:32:47.293 回答