我正在尝试使用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, ®Num);
{
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线程,这看起来很浪费且不一致通常会发生什么。
答案 0 :(得分:3)
在多线程单元中创建类工厂时,可以从多个线程调用它。因此名称为“多线程”。为什么你觉得它令人惊讶?
具体来说,COM运行时维护一个线程池,用于对MTA执行跨公寓调用。然后可以在任何这些线程上调用声明自己为多线程的任何对象。
然后如果我产生了也在STA中的第三个线程,则创建 那个对象会把它放在第一个由com产生的MTA线程中,而不是 一个新的
这句话没有多大意义。多线程对象不属于任何特定线程,因此不清楚“对象......放在MTA线程中”是什么意思。可以创建并调用多线程对象,任何加入MTA的线程(无论是程序创建的线程,还是COM运行时创建的线程);它可能会被几个这样的线程同时调用。
您观察到的行为差异是由于这一事实。跨公寓呼叫以窗口消息的形式传递给STA线程。 STA线程通过调用GetMessage
来表示其准备接受来电。另一方面,跨MTA的跨公寓呼叫不使用窗口消息,而是使用其他一些未记录和未指定的机制。这样的调用只能由来自COM创建的线程池的线程提供 - COM运行时不能只是命令你明确创建的线程,因为它不知道该线程在任何给定时间正在做什么。没有API允许你的线程说“我已经准备好接受并执行任意COM调用” - 加入COM的线程池,实际上。
考虑到这一点,让我们来看看你的场景。案例A:您有一个注册ThreadingModel=Free
的常规COM对象,与自定义类工厂没有任何有趣的业务。 STA线程使用该对象的CoCreateInstance
调用CLSID
。 COM从注册表中读取信息,发现该对象是多线程的,并将调用编组到其MTA线程池中的一个线程,该线程池创建该对象并将其接口指针编组回来。如果STA线程(相同的一个或另一个)再次使用相同的CoCreateInstance
调用CLSID
,则重复该过程,并且可能恰好来自池中的相同线程处理它
顺便说一下,创建对象的线程不必是处理OutputOwningThreadId
调用的相同线程。事实上,如果你连续两次调用OutputOwningThreadId
- 特别是如果你在多个线程的同一个对象上同时调用它 - 很可能会报告不同的线程ID。这是一个误称:在MTA中,没有“拥有线程”这样的东西。
案例B:你旋转你的显式FactoryThread
,它创建了类工厂,然后忙着做某事(事实上它在MTA中旋转消息泵是无关紧要的;它也可以Sleep(INFINITE)
)。该线程是COM运行时的禁止;正如我所说,COM不能在它正在做的任何事情中间神奇地打断它,并让它执行一些COM调用。因此,就像在案例A中一样,所有后续的CreateInstance
和(命名错误的)OutputOwningThreadId
调用都是在来自COM维护的线程池的某些线程上执行的,但从不在FactoryThread
上执行。
是的,在你的方法中,你基本上是在浪费一个线程。对于能够避免注册的好处,这似乎不是一个巨大的代价。