2013-07-24 55 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

相關問題