2013-07-24 62 views
0

我试图使用CoRegisterClassObject来定制我加载的DLL的COM对象在他们的方式。我正在尝试一些解决当线程的公寓类型与com对象不匹配时遇到的问题。基本思想是,由于使用coregisterclassobject在创建com对象时会忽略注册表,所以我需要确保STA对象在STA线程中创建,并且对于MTA对象也是如此。以下是我写作的一个样本,作为概念证明,并不总是按照我的预期行事。COM线程/公寓行为与编组工厂不一致

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初始化为Apartment线程,并且主线程为多线程,那么THREAD_ID_2 == THREAD_ID_3 == THREAD_ID_4!= THREAD_ID_1按预期(工厂正在创建这些对象,并且它们可以存在于工厂线程中)。如果那些线程模型被切换,那么thread_id_3 == thread_id_4,但它们不同于thread_id_2和thread_id_1,即使com对象可以在线程2中创建。

这看起来不一致,并且可能会导致不希望的行为涉及另一个线程。当仅仅依靠注册表而不使用coregisterclassobject时,如果我在STA中创建了一个自由线程对象,该对象将在由MTA生成的com中生成的另一个线程中创建,然后如果我生成第三个线程也是在一个STA中,创建这个对象将会把它放到第一个产生的MTA线程中,而不是一个新的线程(如果对象的线程模型和线程的公寓类型被颠倒过来,也是如此)。但是,如果我要使用coregisterclassobject来创建自己的工厂,并且该对象是多线程的,但线程位于STA中,那么创建这些多线程对象的每个新线程都会生成一个新的MTA线程,这似乎是浪费和不一致的与通常发生的事情。

回答

3

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

具体来说,COM运行时维护一个执行跨公寓呼叫到MTA的线程池。然后可以在任何这些线程上调用任何声明为多线程的对象。

,然后,如果我产生了第三个线程也是在STA,创建 对象将有把它放在第一个COM-催生MTA线程,不 一个新

这声明没有多大意义。多线程对象不属于任何特定线程,因此不清楚“对象...放在...在MTA线程中”的含义。可以创建多线程对象,并调用任何加入MTA的线程(无论您的程序明确创建的线程,还是由COM运行时创建的线程)。它可能同时被几个这样的线程调用。

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

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

顺便说一下,创建该对象的线程不必是处理OutputOwningThreadId调用的相同线程。实际上,如果您连续调用OutputOwningThreadId两次 - 特别是如果您从多个线程同时对同一对象调用它 - 那么报告不同线程ID的几率会很高。这是一个用词不当:在MTA中,不存在“拥有线程”之类的东西。

案例B:你旋转您的明确FactoryThread,它创建类工厂,然后获取忙着做东西(事实上,它的旋转消息泵是在MTA无关;它也可以同样Sleep(INFINITE))。此线程禁止COM运行时;正如我所说的,COM不能在它正在执行的过程中奇迹般地中断它,并让它执行一些COM调用。因此,就像情况A一样,所有后续的CreateInstance和(严重命名)OutputOwningThreadId调用都在COM维护的线程池的某些线程上执行,但从未在FactoryThread上执行。

是的,在你的方法中,你基本上是在浪费一个线程。这似乎并不像一个巨大的代价,能够避免注册表的好处。

+0

明白了。我没有意识到COM与STA和MTA线程的通信方式不同。谢谢你的帮助。 – bdwain

相关问题