OLE 線程模型的描述和工作
本文說明 OLE 線程模型。
原始產品版本: OLE 線程模型
原始 KB 編號: 150777
摘要
COM 物件可用於進程的多個線程中。 「單個線程 Apartment」(STA)和「多線程 Apartment」一詞可用來建立概念架構來描述對象與線程之間的關聯性、物件的並行關聯性、方法呼叫傳遞至物件的方法,以及在線程之間傳遞介面指標的規則。 元件及其用戶端會選擇 COM 目前支援的下列兩種 Apartment 模型:
單個線程 Apartment 模型 (STA):進程中的一或多個線程會使用 COM,COM 會同步處理對 COM 物件的呼叫。 介面會在線程之間封送處理。 單個線程 Apartment 模型的變質案例,其中指定進程中只有一個線程使用 COM,稱為單個線程模型。 先前有時將 STA 模型稱為「公寓模型」。
多線程 Apartment 模型 (MTA):一或多個線程使用 COM 並呼叫與 MTA 相關聯的 COM 物件,是由與 MTA 相關聯的所有線程直接進行,而不需要呼叫端與對象之間任何系統程式代碼的交集。 由於多個同時用戶端可能會同時呼叫物件(同時在多處理器系統上),因此對象必須自行同步處理其內部狀態。 線程之間不會封送處理介面。 先前有時會將此模型稱為「自由線程模型」。
STA 模型和 MTA 模型都可以在同一個進程中使用。 這有時稱為「混合模型」程式。
MTA 是在 NT 4.0 中引進,可在 Windows 95 中使用 DCOM95。 STA 模型存在於 Windows NT 3.51 和 Windows 95 中,以及具有 DCOM95 的 NT 4.0 和 Windows 95。
概觀
COM 中的線程模型會提供機制,讓使用不同線程架構的元件共同運作。 它們也會提供同步處理服務給需要它們的元件。 例如,特定對象的設計只能由單一線程呼叫,而且可能不會同步處理用戶端的並行呼叫。 如果多個線程同時呼叫這類物件,它就會損毀或造成錯誤。 COM 提供處理此線程架構互操作性的機制。
即使是線程感知元件也通常需要同步處理服務。 例如,具有圖形使用者介面(GUI)的元件,例如 OLE/ActiveX 控制件、就地作用中內嵌和 ActiveX 檔,需要同步處理和串行化 COM 呼叫和視窗訊息。 COM 會提供這些同步處理服務,讓這些元件不需要複雜的同步處理程式碼即可撰寫。
一個「公寓」有幾個相互關聯的方面。 首先,這是思考並行的邏輯建構,例如線程與一組 COM 對象的關係。 其次,這是一組程式設計人員必須遵守的規則,才能接收來自 COM 環境的並行行為。 最後,它是系統提供的程式代碼,可協助程式設計人員管理 COM 對象的線程並行。
“Apartment”一詞來自一個隱喻,其中一個過程被設想為一個離散實體,如一個被細分為一組相關但不同的“地區”,稱為“Apartment”。Apartment 是「邏輯容器」,可建立對象之間的關聯,而在某些情況下,則是線程。 線程不是 Apartment,雖然在邏輯上可能會有與 STA 模型中 Apartment 相關聯的單一線程。 物件不是 Apartment,雖然每個物件都與一個和只有一個 Apartment 相關聯。 但公寓不僅僅是一個邏輯結構:其規則會描述 COM 系統的行為。 如果未遵循 Apartment 模型的規則,COM 物件將無法正常運作。
更多詳細資料
單個線程 Apartment (STA) 是一組與特定線程相關聯的 COM 物件。 這些物件是由線程所建立,或更精確地說是先公開至 COM 系統(通常是透過封送處理)在線程上建立,來與 Apartment 產生關聯。 STA 會被視為物件或 Proxy「居住」的地方。如果物件或 Proxy 需要由另一個 Apartment 存取(在相同或不同的進程中),其介面指標必須封送處理至新 Proxy 建立所在的 Apartment。 如果遵循 Apartment 模型的規則,則該物件上不允許來自相同進程中其他線程的直接呼叫;會違反指定 Apartment 中所有物件在單個線程上執行的規則。 規則存在,因為在 STA 中執行的大部分程式代碼在其他線程上執行時將無法正常運作。
與 STA 相關聯的線程必須呼叫 CoInitialize
或 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
,而且必須擷取和分派視窗訊息,讓相關聯的物件接收傳入呼叫。 COM 會使用視窗訊息分派和同步處理 STA 中物件的呼叫,如本文稍後所述。
「主要 STA」是線程,會在指定的行程內呼叫或CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
先呼叫 CoInitialize
。 程式的主要 STA 必須保持運作,直到所有 COM 工作完成為止,因為某些內部程式物件一律會載入主要 STA,如本文稍後所述。
Windows NT 4.0 和 DCOM95 引進了稱為多線程 Apartment (MTA) 的新 Apartment 類型。 MTA 是一組與進程中一組線程相關聯的 COM 物件,因此任何線程都可以直接呼叫任何物件實作,而不需要系統程式代碼的交集。 MTA 中任何物件的介面指標可以在與 MTA 相關聯的線程之間傳遞,而不需要封送處理。 呼叫的進程 CoInitializeEx(NULL, COINIT_MULTITHREADED)
中的所有線程都會與 MTA 相關聯。 與上述 STA 不同,MTA 中的線程不需要擷取和分派相關對象的視窗訊息,才能接收來電。 COM 不會同步處理 MTA 中物件的呼叫。 MTA 中的對象必須透過多個同時線程的互動來防止其內部狀態損毀,而且無法假設不同方法調用之間線程本機記憶體的剩餘常數內容。
進程可以有任意數目的 STA,但最多可以有一個 MTA。 MTA 是由一或多個線程所組成。 STA 各有一個線程。 線程最多屬於一個 Apartment。 對象屬於一個且只有一個 Apartment。 介面指標應該一律在Apartment之間封送處理(雖然封送處理的結果可能是直接指標,而不是 Proxy)。 請參閱下方的資訊 CoCreateFreeThreadedMarshaler
。
進程會選擇 COM 所提供的其中一個線程模型。 STA 模型進程有一或多個 STA,而且沒有 MTA。 MTA 模型進程有一個具有一或多個線程的 MTA,而且沒有任何 STA。 混合模型進程具有一個MTA和任意數目的STA。
單個線程 Apartment 模型
STA 的線程必須呼叫 CoInitialize
或 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
,而且必須擷取和分派視窗訊息,因為 COM 會使用視窗訊息來同步處理和分派此模型中物件呼叫的傳遞。 如需詳細資訊,請參閱下面的 REFERENCES 一節。
支援 STA 模型的伺服器:
在 STA 模型中,COM 會以與張貼至視窗的視窗訊息相同的方式同步處理物件呼叫。 呼叫會使用視窗訊息傳遞至建立物件的線程。 因此,對象的線程必須呼叫 Get
/PeekMessage
和 DispatchMessage
以接收呼叫。 COM 會建立與每個STA相關聯的隱藏視窗。 COM 執行時間會使用張貼至這個隱藏視窗的視窗訊息,將 COM 運行時間從 STA 外部呼叫物件傳送至物件的線程。 當與物件的 STA 相關聯的線程擷取並分派訊息時,COM 也會實作隱藏視窗的視窗程式會接收它。 COM 執行時間會使用視窗程式來「攔截」與 STA 相關聯的線程,因為 COM 執行時間位於從 COM 擁有的線程到 STA 線程的呼叫兩端。 COM 執行時間 (現在在 STA 的線程中執行) 會透過 COM 提供的存根呼叫 「up」 到 物件的對應介面方法。 從方法呼叫傳回的執行路徑會反轉 「up」 呼叫;呼叫會向下傳回存根和 COM 運行時間,它會透過視窗訊息將控制權傳回 COM 運行時間線程,然後透過 COM 通道傳回原始呼叫端。
當多個用戶端呼叫 STA 物件時,呼叫會由 STA 中使用的控制機制傳輸自動排入消息佇列中。 每次物件的 STA 擷取和分派訊息時,都會收到呼叫。 由於呼叫是以這種方式由 COM 同步處理,而且因為呼叫一律會在與物件的 STA 相關聯的單一線程上傳遞,因此物件的介面實作不需要提供同步處理。
注意
如果介面方法實作在處理方法呼叫時擷取和分派訊息,導致相同STA將另一個呼叫傳遞至物件,則可以重新輸入物件。 發生這種狀況的常見方式是,如果 STA 物件使用 COM 進行外向(跨 Apartment/跨進程)呼叫。 這與視窗程式在處理訊息時擷取和分派訊息的方式相同。 COM 不會防止在同一個線程上重新進入,但會防止並行執行。 它也提供可管理 COM 相關重新進入的方法。 如需詳細資訊,請參閱下面的 REFERENCES 一節。 如果方法實作未從其 Apartment 呼叫,或擷取和分派訊息,則不會重新輸入 物件。
STA 模型中的用戶端責任:
在進程和/或線程中執行的用戶端程序代碼,使用STA模型時,必須使用 和 CoGetInterfaceAndReleaseStream
在Apartment CoMarshalInterThreadInterfaceInStream
之間封送處理物件的介面。 例如,如果用戶端中的 Apartment 1 具有介面指標,而 Apartment 2 需要使用它,Apartment 1 必須使用 封送處理介面 CoMarshalInterThreadInterfaceInStream
。 此函式傳回的數據流對像是安全線程,而且其介面指標應該儲存在 Apartment 2 可存取的直接記憶體變數中。 Apartment 2 必須將此數據流介面傳遞至 CoGetInterfaceAndReleaseStream
將基礎物件上的介面取消封存,並取得可存取該物件的 Proxy 指標。
指定程式的主要 Apartment 應該維持運作狀態,直到用戶端完成所有 COM 工作,因為某些內部物件會載入主 Apartment 中。 (詳細資訊詳述如下)。
多線程Apartment模型
MTA 是由進程中的所有線程所建立或公開的物件集合,這些線程已呼叫 CoInitializeEx(NULL, COINIT_MULTITHREADED)
。
注意
COM 目前的實作允許未明確初始化 COM 的線程成為 MTA 的一部分。 只有在進程至少有一個其他線程之前呼叫 CoInitializeEx(NULL, COINIT_MULTITHREADED)
之後,才會啟動使用 COM 時,不會初始化 COM 的線程是 MTA 的一部分。 (即使 COM 本身可能尚未明確完成任何用戶端線程,就可能已初始化 MTA;例如,與 STA 呼叫 CoGetClassObject
/CoCreateInstance[Ex]
相關聯的線程在標示為 “ThreadingModel=Free” 的 CLSID 上,COM 會隱含地建立載入類別物件的 MTA。請參閱下列線程模型互操作性的相關信息。
不過,這是一種設定,在某些情況下可能會導致問題,例如存取違規。 因此,建議每個需要執行 COM 工作的線程呼叫 ,然後在 COM 工作完成時呼叫 CoUninitialize
來初始化 COMCoInitializeEx
。 「不必要」初始化 MTA 的成本最低。
MTA 線程不需要擷取和分派訊息,因為 COM 不會使用此模型中的視窗訊息來傳遞對物件的呼叫。
支援 MTA 模型的伺服器:
在 MTA 模型中,COM 不會同步處理對對對象的呼叫。 多個用戶端可以在不同的線程上同時呼叫支援此模型的物件,而且對象必須使用事件、Mutex、信號等同步處理物件,在其介面/方法實作中提供同步處理。MTA 物件可以透過屬於物件進程的 COM 建立線程集區,從多個跨進程用戶端接收並行呼叫。 MTA 物件可以在與 MTA 相關聯的多個線程上,從多個同進程用戶端接收並行呼叫。
MTA 模型中的用戶端責任:
在進程和/或線程中執行且使用 MTA 模型的用戶端程式代碼不需要封送處理本身與其他 MTA 線程之間物件的介面指標。 相反地,一個 MTA 線程可以使用從另一個 MTA 線程取得的介面指標作為直接記憶體指標。 當用戶端線程呼叫跨進程物件時,它會暫停直到呼叫完成為止。 呼叫可能會到達與 MTA 相關聯的物件,而與 MTA 相關聯的所有應用程式建立線程都會在外出呼叫時遭到封鎖。 在此情況下,一般而言,連入呼叫會在 COM 運行時間提供的線程上傳遞。 訊息篩選器 (
IMessageFilter
) 不適用於 MTA 模型。
混合線程模型
支援混合線程模型的進程將會使用一個 MTA 和一或多個 STA。 介面指標必須在所有 Apartment 之間封送處理,但可以在 MTA 內不使用封送處理。 對 STA 中物件的呼叫會由 COM 同步處理,在 MTA 中呼叫物件時,只會在一個線程上執行。 不過,從STA到MTA的呼叫通常會經過系統提供的程序代碼,並在傳遞至物件之前,先從STA線程切換到 MTA 線程。
注意
CoCreateFreeThreadedMarshaler()
如需可使用直接指標的情況,以及下列 API 的討論,以及 STA 線程如何直接呼叫到與 MTA 相關聯的物件,反之亦然,請參閱下列 API 的相關文件。
選擇線程模型
元件可以選擇使用混合線程模型來支援 STA 模型、MTA 模型或兩者的組合。 例如,執行大量 I/O 的物件可以選擇支援 MTA,藉由允許在 I/O 延遲期間進行介面呼叫,以提供用戶端的最大回應。 或者,與用戶互動的對象幾乎一律會選擇支援 STA,以同步處理連入 COM 呼叫與其 GUI 作業。 因為 COM 提供同步處理,因此支援 STA 模型會比較容易。 支援 MTA 模型比較困難,因為對象必須實作同步處理,但對用戶端的回應比較好,因為同步處理用於較小的程式碼區段,而不是 COM 所提供的整個介面呼叫。
STA 模型也會由Microsoft交易伺服器使用(MTS,先前稱為“Viper”的程式代碼),因此規劃在 MTS 環境中執行的 DLL 型對象應該使用 STA 模型。 針對 MTA 模型實作的物件通常會在 MTS 環境中正常運作。 不過,它們的執行效率會較低,因為它們會使用不必要的線程同步處理基本類型。
標記內部伺服器支援的線程模型
如果線程呼叫 CoInitializeEx(NULL, COINIT_MULTITHREADED)
或使用 COM 而不初始化它,線程就會使用 MTA 模型。 如果線程呼叫 CoInitialize
或 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED)
,線程就會使用 STA 模型。
CoInitialize
API 提供用戶端程式代碼和封裝於 中的物件的 Apartment 控制件。EXE,因為 COM 運行時間的啟動程式代碼可以用所需的方式初始化 COM。
不過,內部程式 (DLL 型) COM 伺服器不會呼叫 CoInitialize
/CoInitializeEx
,因為載入 DLL 伺服器時會呼叫這些 API。 因此,DLL 伺服器必須使用登錄來通知 COM 它支援的線程模型,讓 COM 能夠確保系統以與其相容的方式運作。 元件的 CLSID\InprocServer32 索引鍵 ThreadingModel
具名值會用於此用途,如下所示:
ThreadingModel
值不存在:支持單個線程模型。ThreadingModel=Apartment
:支援 STA 模型。ThreadingModel=Both
:支援 STA 和 MTA 模型。ThreadingModel=Free
:僅支援 MTA。
注意
ThreadingModel
是具名值,而不是 InprocServer32 的子機碼,如某些舊版 Win32 檔中所述。
本文稍後會討論內部伺服器的線程模型。 如果內部伺服器提供許多類型的物件(每個物件都有自己的唯一 ThreadingModel
CLSID),則每個類型都可以有不同的值。 換句話說,線程模型是每個CLSID,而不是每個程式代碼套件/DLL。 不過,對於支援多個線程的任何內部伺服器而言,「啟動程式」和查詢所有內部伺服器所需的 API 進入點都必須安全線程(DLLGetClassObject()
DLLCanUnloadNow()
這表示 ThreadingModel
Apartment、Both 或 Free 的值)。
如先前所述,跨進程伺服器不會使用 ThreadingModel 值來標示自己。 相反地,它們會使用 CoInitialize
或 CoInitializeEx
。 預期使用 COM 之「代理」功能的 DLL 型伺服器(例如系統提供的代理程式DLLHOST.EXE)遵循 DLL 型伺服器的規則;在此情況下,沒有任何特殊考慮。
當客戶端和物件使用不同的線程模型時
即使使用不同的線程模型,用戶端與跨進程對象之間的互動也是直接的,因為客戶端和對象位於不同的進程中,而且 COM 涉及將呼叫從客戶端傳遞至物件。 由於 COM 在用戶端與伺服器之間是插補的,因此它會提供程式碼來互操作線程模型。 例如,如果多個STA或 MTA 用戶端同時呼叫 STA 物件,COM 會將對應的視窗訊息放在伺服器消息佇列中來同步處理呼叫。 每次擷取和分派訊息時,物件的STA都會收到一個呼叫。 允許線程模型互操作性的所有組合,並在用戶端與跨進程對象之間完全支援。
用戶端與使用不同線程模型之內部對象之間的互動更為複雜。 雖然伺服器是內部伺服器,但在某些情況下,COM 必須在用戶端與 對象之間交集。 例如,設計來支援STA模型的內程式物件可由用戶端的多個線程同時呼叫。 COM 無法允許用戶端線程直接存取物件的介面,因為物件不是針對這類並行存取所設計。 相反地,COM 必須確定呼叫會同步處理,而且只有與 「contains」 對象相關聯的線程才會進行。 儘管複雜度增加,客戶端與內部對象之間仍允許線程模型互操作性的所有組合。
跨進程線程模型 (EXE 型) 伺服器
以下是三種跨進程伺服器類別,不論該用戶端所使用的線程模型為何,每個伺服器都可以由任何 COM 用戶端使用:
STA Model Server:
伺服器會在一或多個STA中運作 COM。 連入呼叫會由 COM 同步處理,並由與建立物件所在的 STA 相關聯的線程傳遞。 Class-Factory 方法呼叫是由註冊類別處理站之 STA 相關聯的線程所傳遞。 對象和類別處理站不需要實作同步處理。 不過,實作者必須同步存取多個STA所使用的任何全域變數。 伺服器必須使用
CoMarshalInterThreadInterfaceInStream
和CoGetInterfaceAndReleaseStream
來封送處理 STA 之間可能來自其他伺服器的介面指標。 伺服器可以選擇性地在每個 STA 中實IMessageFilter
作,以控制 COM 呼叫傳遞的各個層面。 變質案例是單一線程模型伺服器,可讓 COM 在一個 STA 中運作。MTA Model Server:
伺服器會在一或多個線程中運作,這些線程全都屬於 MTA。 COM 不會同步呼叫。 COM 會在伺服器進程中建立線程集區,而用戶端呼叫是由其中任何一個線程傳遞。 線程不需要擷取和分派訊息。 對象和類別處理站必須實作同步處理。 伺服器不需要封送處理線程之間的介面指標。
混合模型伺服器:
如需詳細資訊,請參閱本文標題為「混合線程模型」的章節。
用戶端中的線程模型
用戶端有三種類別:
STA 模型用戶端:
用戶端會在與一或多個STA相關聯的線程中運作 COM。 用戶端必須使用
CoMarshalInterThreadInterfaceInStream
和CoGetInterfaceAndReleaseStream
來封送處理 STA 之間的介面指標。 變質案例是單一線程模型用戶端,可讓 COM 在一個 STA 中運作。 用戶端的線程在進行傳出呼叫時,會進入 COM 提供的訊息迴圈。 用戶端可用來IMessageFilter
管理視窗訊息的回呼和處理,同時等候外出呼叫和其他並行問題。MTA 模型用戶端:
用戶端會讓 COM 在一或多個線程中運作,這些線程全都屬於 MTA。 用戶端不需要封送處理其線程之間的介面指標。 用戶端無法使用
IMessageFilter
。 客戶端的線程會在對跨進程對象進行 COM 呼叫時暫停,並在呼叫傳回時繼續。 連入呼叫會抵達 COM 建立和受控線程。混合模型用戶端:
如需詳細資訊,請參閱本文標題為「混合線程模型」的章節。
內部線程中的線程模型 (DLL 型) 伺服器
內部伺服器有四種類別,不論該用戶端所使用的線程模型為何,每個伺服器都可以由任何 COM 用戶端使用。 不過,內部伺服器必須針對它們實作的任何自定義(非系統定義)介面提供封送處理程序代碼,如果他們支援線程模型互操作性,因為這通常需要 COM 封送處理用戶端 Apartment 之間的介面。 這四個類別包括:
支援單個線程處理 (“main” STA) 的 In-proc Server- 沒有
ThreadingModel
值:此伺服器所提供的物件預期會由所建立的相同用戶端 STA 存取。 此外,伺服器預期其所有進入點,例如
DllGetClassObject
和DllCanUnloadNow
,以及相同的線程存取全域數據(與主要 STA 相關聯的進入點)。 在 COM 中引進多線程之前存在的伺服器在此類別中。 這些伺服器不是設計成由多個線程存取,因此 COM 會在進程的主要 STA 中建立伺服器提供的所有物件,而對對物件的呼叫是由與主要 STA 相關聯的線程所傳遞。 其他用戶端 Apartment 可透過 Proxy 存取物件。 來自其他 Apartment 的呼叫會從 Proxy 移至主要 STA 中的存根(線程間封送處理),然後傳送至 物件。 此封送處理可讓 COM 同步處理對物件的呼叫,而呼叫是由建立物件的 STA 所傳遞。 線程間封送處理相對於直接呼叫的速度很慢,因此建議重寫這些伺服器以支援多個STA (類別 2)。支援單個線程 Apartment 模型 (多個 STA) 的 In-proc Server - 標示為
ThreadingModel=Apartment
:此伺服器所提供的物件預期會由所建立的相同用戶端 STA 存取。 因此,它類似於單個線程內部伺服器所提供的物件。 不過,此伺服器所提供的物件可以在進程的多個 STA 中建立,因此伺服器必須設計其進入點,例如
DllGetClassObject
和DllCanUnloadNow
,以及用於多線程使用的全域數據。 例如,如果進程的兩個STA同時建立兩個in-proc物件實例,DllGetClassObject
則這兩個STA都可以同時呼叫。 同樣地,必須撰寫 ,DllCanUnloadNow
如此一來,當程式代碼仍在伺服器中執行時,伺服器就會受到保護,使其無法卸除。如果伺服器只提供一個類別處理站實例來建立所有物件,則類別處理站實作也必須針對多線程使用而設計,因為它是由多個用戶端 STA 存取。 如果每次
DllGetClassObject
呼叫時伺服器都會建立類別處理站的新實例,則類別處理站不需要安全線程。 不過,實作者必須同步存取任何全域變數。類別處理站所建立的 COM 物件不需要安全線程。 不過,實作者必須同步存取全域變數。 線程建立之後,一律會透過該線程存取物件,而且 COM會同步處理對物件的所有呼叫。 與建立物件的 STA 不同的用戶端 Apartment 必須透過 Proxy 存取物件。 當用戶端封送處理其 Apartment 之間的介面時,就會建立這些 Proxy。
透過其類別處理站建立 STA 物件的任何客戶端都會取得物件的直接指標。 這與單個線程內部物件不同,其中只有用戶端的主要 STA 會取得物件的直接指標,而建立物件的其他所有 STA 都會透過 Proxy 存取物件。 由於線程間封送處理相對於直接呼叫的速度很慢,因此可以藉由變更單一線程內部伺服器以支援多個 STA 來改善速度。
只支援 MTA 的 In-proc Server - 標示為
ThreadingModel=Free
:此伺服器所提供的物件僅適用於 MTA。 它會實作自己的同步處理,並同時由多個用戶端線程存取。 此伺服器的行為可能與 STA 模型不相容。 (例如,藉由使用 Windows 消息佇列的方式中斷 STA 的訊息幫浦。此外,藉由將對象的線程模型標示為「免費」,對象的實作者會指出下列事項:這個物件可以從任何用戶端線程呼叫,但此物件也可以直接將介面指標(而不封送處理)傳遞至它建立的任何線程,而且這些線程可以透過這些指標進行呼叫。 因此,如果用戶端將介面指標傳遞至用戶端實作的物件(例如接收)至這個物件,則可以選擇從它建立的任何線程透過這個介面指標回呼。 如果用戶端是 STA,則來自線程的直接呼叫,這與建立接收對象的線程不同,將會發生錯誤(如上述 2 所示)。 因此,COM 一律可確保與 STA 相關聯的線程中的用戶端只能透過 Proxy 存取這類內部物件。 此外,這些物件不應該與自由線程封送處理器匯總,因為這可讓它們直接在 STA 線程上執行。
支援 Apartment 模型和自由線程的 In-proc Server - 標示為
ThreadingModel=Both
:此伺服器所提供的物件會實作自己的同步處理,並同時由多個用戶端 Apartment 存取。 此外,此物件會在 STA 或用戶端程式的 MTA 中直接建立及使用,而不是透過 Proxy 使用。 由於此物件直接用於 STA 中,因此伺服器必須在線程之間封送處理物件介面,可能來自其他伺服器,因此保證其以線程適當的方式存取任何物件。 此外,藉由將對象的線程模型標示為 「Both」,對象的實作器會指出下列事項:這個物件可以從任何用戶端線程呼叫,但從這個對象到用戶端的任何回呼只會在物件接收回呼物件的介面指標所在的 Apartment 上完成。 COM 可讓這類物件直接在STA和用戶端程式的MTA中建立。
因為任何建立這類物件的 Apartment 一律會取得直接指標,而不是 Proxy 指標,
ThreadingModel "Both"
因此物件會在載入 STA 時提供物件的效能改善ThreadingModel "Free"
。ThreadingModel "Both"
因為物件也是針對 MTA 存取所設計,所以它可以藉由匯總 所提供的CoCreateFreeThreadedMarshaler
封送處理器來加速效能。 這個系統提供的對象會匯總至任何呼叫的物件,而自定義封送處理會將物件的直接指標匯入程式中的所有Apartment。 任何 Apartment 中的用戶端,無論是 STA 或 MTA,則可以直接存取物件,而不是透過 Proxy。 例如,STA 模型用戶端會在 STA1 中建立內部物件,並將物件封送處理至 STA2。 如果物件未使用自由線程封送處理器進行匯總,STA2 會透過 Proxy 取得物件的存取權。 如果這樣做,自由線程封送處理器會提供STA2 與物件的直接指標注意
使用自由線程封送處理器匯總時,必須小心。 例如,假設標示為
ThreadingModel "Both"
的物件(以及使用自由線程封送處理器匯總)具有數據成員,該成員是另一ThreadingModel
個對象的介面指標,其為 “Apartment”。 然後假設 STA 會建立第一個物件,並在建立期間,第一個物件會建立第二個物件。 根據上述規則,第一個對象現在會持有第二個物件的直接指標。 現在假設 STA 會將第一個物件的介面指標封送處理至另一個 Apartment。 由於第一個物件會使用自由線程封送處理器匯總,因此第一個物件的直接指標會提供給第二個 Apartment。 如果第二個 Apartment 接著透過這個指標呼叫 ,而且如果這個呼叫導致第一個物件透過介面指標呼叫第二個物件,則發生錯誤,因為第二個物件不是直接從第二個 Apartment 直接呼叫。 如果第一個物件持有第二個物件的 Proxy 指標,而不是直接指標,這會造成不同的錯誤。 系統 Proxy 也是與一個和只有一個 Apartment 相關聯的 COM 物件。 他們跟蹤他們的公寓,以避免某些迴圈。 因此,與對象執行所在的線程相關聯的 Proxy 上呼叫 的物件,將會收到 Proxy 傳回RPC_E_WRONG_THREAD,而且呼叫將會失敗。
用戶端與進程內對象之間的線程模型互操作性
用戶端與進程內對象之間允許線程模型互操作性的所有組合。
COM 允許所有 STA 模型用戶端透過在用戶端的主要 STA 中建立和存取 物件,並將其封送處理至呼叫 CoCreateInstance[Ex]
的用戶端 STA,以與單一線程內部物件互動。
如果用戶端中的 MTA 在程式伺服器中建立 STA 模型,COM 會在用戶端中啟動「主機」STA。 此主機 STA 會建立 物件,並將介面指標封送處理回 MTA。 同樣地,當 STA 在程式內建立 MTA 伺服器時,COM 會啟動主機 MTA,在其中建立物件並封送處理回 STA。 單個線程模型與 MTA 模型之間的互操作性會以類似的方式處理,因為單個線程模型只是 STA 模型的變質案例。
如果內部伺服器想要支援需要 COM 在用戶端 Apartment 之間封送處理介面的互操作性,則必須為內部伺服器實作的任何自定義介面提供封送處理程式代碼。 如需詳細資訊,請參閱下面的 REFERENCES 一節。
線程模型與 Class Factory 物件之間傳回的關聯性
下列兩個步驟說明「載入」Apartment 的內程式伺服器精確定義:
如果操作系統載入器先前尚未載入包含內部伺服器類別的 DLL(對應至行程位址空間),則會執行該作業,而 COM 會取得 DLL 所匯出之函式的
DLLGetClassObject
位址。 如果先前已由任何 Apartment 相關聯的線程載入 DLL,則會略過此階段。COM 會使用與「載入」Apartment 相關聯的 MTA 線程之一,來呼叫
DllGetClassObject
DLL 所導出的函式,要求所需類別的 CLSID。 然後,傳回的 Factory 物件會用來建立 類別對象的實例。第二個步驟(COM 的
DllGetClassObject
呼叫)會在每次用戶端呼叫CoGetClassObject
/CoCreateIntance[Ex]
時發生,即使在相同的 Apartment 內也一樣。 換句話說,DllGetClassObject
可能會由與相同 Apartment 相關聯的線程呼叫多次;這一切都取決於該 Apartment 中有多少用戶端嘗試存取該類別的 Class Factory 物件。
類別實作的作者,最後,指定類別集合之 DLL 套件的作者可以完全自由地決定要傳回哪些 Factory 物件以回應 DllGetClassObject
函式呼叫。 DLL 套件的作者具有最終發言權,因為單一 DLL 全 DllGetClassObject()
DLL 進入點的程式代碼「後置」是決定該怎麼做的第一個潛在最終許可權。 這三個典型的可能性如下:
DllGetClassObject
會針對每個呼叫線程傳回唯一的 Class Factory 物件(這表示每個 STA 的一個類別 Factory 物件,以及 MTA 內可能有多個類別處理站物件)。DllGetClassObject
不論呼叫線程的身分識別或與呼叫線程相關聯的 Apartment 類型為何,一律會傳回相同的類別 Factory 物件。DllGetClassObject
會傳回每個呼叫 Apartment 的唯一類別 Factory 物件(STA 和 MTA 中的每個 Apartment 一個)。
呼叫 DllGetClassObject
與傳回的 Class Factory 物件之間有其他關聯性的可能性(例如每次呼叫 DllGetClassObject
的新類別 Factory 物件),但目前似乎並不有用。
內部伺服器客戶端和物件線程模型的摘要
下表摘要說明當用戶端線程第一次在實作為內部伺服器之類別上呼叫 CoGetClassObject
時,不同線程模型之間的互動。
用戶端/線程種類:
- 用戶端正在與 “main” STA 相關聯的線程中執行(第一個要呼叫
CoInitialize
或CoInitializeEx
具有COINIT_APARTMENTTHREADED
旗標的線程)-呼叫此 STA0 (也稱為單個線程模型)。 - 用戶端正在與任何其他 STA [ASCII 150] 中相關聯的線程中執行,呼叫此 STA*。
- 用戶端正在與 MTA 相關聯的線程中執行。
DLL 伺服器種類:
- 伺服器沒有
ThreadingModel
索引鍵--呼叫此「無」。 - 伺服器標示為 「Apartment」--呼叫此 「Apt」。
- 伺服器標示為「免費」。
- 伺服器標示為「兩者」。
閱讀下表時,請記住上述「將伺服器載入」到 Apartment 的定義。
Client Server Result
STA0 None Direct access; server loaded into STA0
STA* None Proxy access; server loaded into STA0.
MTA None Proxy access; server loaded into STA0; STA0 created automatically by COM if necessary;
STA0 Apt Direct access; server loaded into STA0
STA* Apt Direct access; server loaded into STA*
MTA Apt Proxy access; server loaded into an STA created automatically by COM.
STA0 Free Proxy access; server is loaded into MTA MTA created automatically by COM if necessary.
STA* Free Same as STA0->Free
MTA Free Direct access
STA0 Both Direct access; server loaded into STA0
STA* Both Direct access; server loaded into STA*
MTA Both Direct access; server loaded into the MTA
參考資料
和 IMessageFilter
介面上的 CoRegisterMessageFilter()
SDK 檔。