共用方式為


OLE 線程模型的描述和運作

本文說明 OLE 線程模型。

原始產品版本: OLE 線程模型
原始 KB 編號: 150777

摘要

COM 物件可用於進程的多個線程。 「單個線程 Apartment」一詞 (STA) 和「多線程 Apartment」 (MTA) 用來建立概念架構,以描述對象與線程之間的關聯性、對象之間的並行關聯性、方法呼叫傳遞至物件的方式,以及在線程之間傳遞介面指標的規則。 元件及其用戶端會在 COM 目前支援的下列兩個 Apartment 模型之間進行選擇:

  1. 單個線程 Apartment 模型 (STA) :進程中的一或多個線程使用 COM,COM 會同步呼叫 COM 物件。 介面會在線程之間封送處理。 單個線程 Apartment 模型的變質案例,其中指定進程中只有一個線程使用 COM,稱為單個線程模型。 先前有時只將 STA 模型稱為「Apartment 模型」。

  2. 多線程 Apartment 模型 (MTA) :一或多個線程使用 COM,而與 MTA 相關聯之 COM 物件的呼叫會直接由與 MTA 相關聯的所有線程進行,而不會在呼叫端和對象之間插入任何系統程序代碼。 由於多個同時用戶端可能會在多處理器系統) 上多次或更少同時呼叫物件 (,因此對象必須自行同步處理其內部狀態。 介面不會在線程之間封送處理。 先前有時將此模型稱為「自由線程模型」。

  3. STA 模型和 MTA 模型都可以在相同的程式中使用。 這有時稱為「混合模型」程式。

MTA 是在 NT 4.0 中引進,並可在具有 DCOM95 的 Windows 95 中使用。 STA 模型存在於 Windows NT 3.51 和 Windows 95 中,以及 NT 4.0 和 Windows 95 與 DCOM95 中。

概觀

COM 中的線程模型提供機制,讓使用不同線程架構的元件一起運作。 它們也會為需要它們的元件提供同步處理服務。 例如,特定物件可能設計成只由單一線程呼叫,而且可能不會同步處理來自用戶端的並行呼叫。 如果多個線程同時呼叫這類物件,則會當機或造成錯誤。 COM 提供處理線程架構之此互操作性的機制。

即使是線程感知元件通常也需要同步處理服務。 例如,具有圖形使用者介面 (GUI) 的元件,例如 OLE/ActiveX 控制件、就地使用中的內嵌和 ActiveX 檔,都需要同步處理和串行化 COM 呼叫和視窗訊息。 COM 提供這些同步處理服務,讓這些元件不需要複雜的同步處理程式碼即可撰寫。

「Apartment」有數個相互關聯的層面。 首先,它是考慮並行的邏輯建構,例如線程與一組 COM 對象的關係。 其次,這是程式設計人員必須遵守的一組規則,才能從 COM 環境接收預期的並行行為。 最後,它是系統提供的程式代碼,可協助程式設計人員管理與 COM 物件相關的線程並行存取。

「Apartment」一詞來自一個隱喻,其中進程會被當作離散實體來宣告,例如「建築物」,其細分為一組相關但不同的「地區設定」,稱為「住宅」。Apartment 是「邏輯容器」,可建立物件與在某些情況下線程之間的關聯。 雖然 STA 模型中可能有單一線程以邏輯方式與 Apartment 相關聯,但線程不是 Apartment。 雖然每個物件都與一個且只有一個 Apartment 相關聯,但物件不是 Apartment。 但是,住宅不只是邏輯建構;其規則描述 COM 系統的行為。 如果未遵循 Apartment 模型的規則,COM 物件將無法正常運作。

其他詳細資料

單個線程 Apartment (STA) 是一組與特定線程相關聯的 COM 物件。 這些物件會藉由線程建立,或更精確地先向 COM 系統公開, (通常透過封送處理線程上的) 來與 Apartment 相關聯。 STA 會被視為物件或 Proxy「存在」的地方。如果物件或 Proxy 需要由位於相同或不同進程) 中的另一個 Apartment (存取,則其介面指標必須封送處理至建立新 Proxy 所在的 Apartment。 如果遵循 Apartment 模型的規則,則該物件上不允許來自相同進程中其他線程的直接呼叫;這會違反指定 Apartment 內的所有物件在單一線程上執行的規則。 規則之所以存在,是因為在 STA 中執行的大部分程式代碼,如果在其他線程上執行,將無法正常運作。

與 STA 相關聯的線程必須呼叫 CoInitializeCoInitializeEx(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 中的對象必須透過多個同時線程的互動來保護其內部狀態免於損毀,而且無法在不同方法調用之間對 Thread-Local Storage 剩餘常數的內容進行任何假設。

進程可以有任意數目的 STA,但最多可以有一個 MTA。 MTA 包含一或多個線程。 STA 各有一個線程。 線程最多屬於一個 Apartment。 物件只屬於一個 Apartment。 雖然封送處理的結果可能是直接指標,而不是 Proxy) ,但介面指標應該一律在住宅 (之間封送處理。 請參閱下列有關 CoCreateFreeThreadedMarshaler的資訊。

進程會選擇 COM 提供的其中一個線程模型。 STA 模型進程有一或多個 STA,而且沒有 MTA。 MTA 模型進程有一個具有一或多個線程的 MTA,而且沒有任何 STA。 混合模型進程有一個 MTA 和任意數目的 STA。

單個線程 Apartment 模型

STA 的線程必須呼叫 CoInitializeCoInitializeEx(NULL, COINIT_APARTMENTTHREADED) ,而且必須擷取和分派視窗訊息,因為 COM 會使用視窗訊息來同步處理呼叫並分派給此模型中的物件。 如需詳細資訊,請參閱下方的 REFERENCES 一節。

支援 STA 模型的伺服器:

在 STA 模型中,COM 會以與張貼至視窗的視窗訊息同步處理的方式同步處理物件的呼叫。 呼叫會使用視窗訊息傳遞至建立 對象的線程。 因此,對象的線程必須呼叫 Get/PeekMessageDispatchMessage 才能接收呼叫。 COM 會建立與每個STA相關聯的隱藏視窗。 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 模型的線程中執行的用戶端程式代碼,必須使用 CoMarshalInterThreadInterfaceInStreamCoGetInterfaceAndReleaseStream,在apartment之間封送處理物件的介面。 例如,如果用戶端中的 Apartment 1 有介面指標,而 Apartment 2 需要使用它,Apartment 1 就必須使用 CoMarshalInterThreadInterfaceInStream封送處理介面。 此函式傳回的數據流對像是安全線程,而且其介面指標應該儲存在 Apartment 2 可存取的直接記憶體變數中。 Apartment 2 必須將此數據流介面傳遞至 , CoGetInterfaceAndReleaseStream 以解除解壓縮基礎物件上的介面,並取得 Proxy 的指標,以透過該 Proxy 存取物件。

指定進程的主要 Apartment 應該會保持運作,直到用戶端完成所有 COM 工作為止,因為某些程式內物件會載入 main-apartment 中。 (詳細資訊請參閱下) 。

多線程Apartment模型

MTA 是進程中所有已呼叫 CoInitializeEx(NULL, COINIT_MULTITHREADED)之線程所建立或公開的物件集合。

注意事項

COM 目前的實作可讓未明確初始化 COM 的線程成為 MTA 的一部分。 只有在進程中至少有一個線程先前已呼叫 CoInitializeEx(NULL, COINIT_MULTITHREADED)之後開始使用 COM 時,未初始化 COM 的線程才是 MTA 的一部分。 (甚至有可能在沒有任何用戶端線程明確完成時,COM 本身可能已初始化 MTA;例如,與標示為 「ThreadingModel=Free」 的 CLSID 上 STA 呼叫 CoGetClassObject/CoCreateInstance[Ex] 相關聯的線程,以及 COM 隱含建立載入類別物件的 MTA。) 請參閱下方線程模型互操作性的資訊。

不過,此組態在某些情況下可能會造成問題,例如存取違規。 因此,建議每個需要執行 COM 的線程呼叫 來初始化 COM CoInitializeEx ,然後在 COM 工作完成時呼叫 CoUninitialize。 「不必要」初始化 MTA 的成本很小。

MTA 線程不需要擷取和分派訊息,因為 COM 不會使用此模型中的視窗訊息將呼叫傳遞至物件。

  • 支援 MTA 模型的伺服器:

    在 MTA 模型中,COM 不會同步呼叫物件。 多個用戶端可以同時呼叫在不同線程上支援此模型的物件,而且對象必須使用事件、Mutex、號誌等同步處理物件,在其介面/方法實作中提供同步處理。MTA 物件可以透過屬於物件進程的 COM 建立線程集區,接收來自多個跨進程用戶端的並行呼叫。 MTA 物件可以在與 MTA 相關聯的多個線程上,接收來自多個同進程用戶端的並行呼叫。

  • MTA 模型中的用戶端責任:

    在使用 MTA 模型的進程和/或線程中執行的用戶端程式代碼,不需要在本身與其他 MTA 線程之間封送處理物件的介面指標。 相反地,一個 MTA 線程可以使用從另一個 MTA 線程取得的介面指標作為直接記憶體指標。 當用戶端線程呼叫跨進程物件時,它會暫停,直到呼叫完成為止。 呼叫可能會抵達與 MTA 相關聯的物件,而與 MTA 相關聯的所有應用程式建立線程都會在進行中的呼叫時遭到封鎖。 在此情況下和一般情況下,傳入呼叫會在 COM 運行時間所提供的線程上傳遞。 訊息篩選 (IMessageFilter) 無法在 MTA 模型中使用。

混合線程模型

支援混合線程模型的進程將會使用一個 MTA 和一或多個 STA。 介面指標必須在所有住宅之間封送處理,但不需在 MTA 內封送處理即可使用。 對 STA 中物件的呼叫會由 COM 同步處理,只在一個線程上執行,而不會呼叫 MTA 中的物件。 不過,從 STA 呼叫 MTA 通常會經過系統提供的程式代碼,然後從 STA 線程切換至 MTA 線程,再傳遞至 物件。

注意事項

如需可使用直接指標的情況,以及 STA 線程如何直接呼叫與 MTA 相關聯的物件,反之亦然,請參閱以下的 SDK 檔 CoCreateFreeThreadedMarshaler() 和該 API 的討論。

選擇線程模型

元件可以選擇使用混合線程模型來支援 STA 模型、MTA 模型或兩者的組合。 例如,執行大量 I/O 的物件可以選擇支援 MTA,以允許在 I/O 延遲期間進行介面呼叫,以提供最大回應給用戶端。 或者,與用戶互動的對象幾乎一律會選擇支援 STA,以同步處理連入 COM 呼叫與其 GUI 作業。 支援STA模型比較容易,因為 COM提供同步處理。 支援 MTA 模型比較困難,因為對象必須實作同步處理,但是對用戶端的回應比較好,因為同步處理是用於較小的程式碼區段,而不是 COM 所提供的整個介面呼叫。

STA 模型也由 Microsoft Transaction Server (MTS 使用,先前以程式代碼命名為 “Viper”) ,因此規劃在 MTS 環境中執行的 DLL 型對象應該使用 STA 模型。 針對 MTA 模型實作的物件通常可在 MTS 環境中正常運作。 不過,它們的執行效率較低,因為它們會使用不必要的線程同步處理基本類型。

標示 In-Proc 伺服器的支援線程模型

如果線程在不初始化的情況下呼叫 CoInitializeEx(NULL, COINIT_MULTITHREADED) 或使用 COM,則會使用 MTA 模型。 如果線程呼叫 CoInitializeCoInitializeEx(NULL, COINIT_APARTMENTTHREADED),則會使用 STA 模型。

API CoInitialize 提供用戶端程式代碼和封裝物件的 Apartment 控制件。EXE,因為 COM 運行時間的啟動程式代碼可以以所需的方式初始化 COM。

不過,進程內 (以 DLL 為基礎的) COM 伺服器不會呼叫 CoInitialize/CoInitializeEx ,因為這些 API 會在載入 DLL 伺服器時呼叫。 因此,DLL 伺服器必須使用登錄來通知 COM 它支援的線程模型,讓 COM 可以確保系統以與其相容的方式運作。 元件 CLSID\InprocServer32 金鑰的具名值會 ThreadingModel 用於此用途,如下所示:

  • ThreadingModel 值不存在:支持單個線程模型。
  • ThreadingModel=Apartment:支援 STA 模型。
  • ThreadingModel=Both:支援 STA 和 MTA 模型。
  • ThreadingModel=Free:僅支援 MTA。

注意事項

ThreadingModel 是具名值,而不是 InprocServer32 的子機碼,如某些舊版 Win32 檔中未正確記載。

本文稍後會討論進程內伺服器的線程模型。 如果進程內伺服器提供許多類型的物件, (每個物件都有自己的唯一 ThreadingModel CLSID) ,則每個類型都可以有不同的值。 換句話說,線程模型是每個CLSID,而不是每個程式代碼套件/DLL。 不過,對於支援多個線程的任何同進程伺服器而言,) 必須是安全線程 (表示 ThreadingModel Apartment、Both 或 Free) 值的任何進程內伺服器,「啟動載入」和查詢所有進程內伺服器 DLLGetClassObject()DLLCanUnloadNow() (所需的 API 進入點。

如先前所述,跨進程伺服器不會使用 ThreadingModel 值來標記本身。 相反地,它們會使用 CoInitializeCoInitializeEx。 預期使用 COM (的「代理」功能執行跨進程的 DLL 型伺服器,例如系統提供的代理 DLLHOST.EXE) 遵循以 DLL 為基礎的伺服器的規則;在此情況下,沒有任何特殊考慮。

當客戶端和物件使用不同的線程模型時

即使使用不同的線程模型,用戶端與跨進程對象之間的互動也相當直接,因為客戶端和對象位於不同的進程中,而 COM 涉及將呼叫從客戶端傳遞至 物件。 由於 COM 在用戶端與伺服器之間交互,因此會提供線程模型互操作的程式代碼。 例如,如果 STA 物件由多個 STA 或 MTA 用戶端同時呼叫,COM 會將對應的視窗訊息放在伺服器的消息佇列中,以同步處理呼叫。 物件的STA會在每次擷取和分派訊息時收到一個呼叫。 用戶端與跨進程對象之間允許並完全支援線程模型互操作性的所有組合。

用戶端與使用不同線程模型的進程內對象之間的互動更為複雜。 雖然伺服器為進程內,但在某些情況下,COM 必須在用戶端與 對象之間交集本身。 例如,用戶端的多個線程可以同時呼叫專為支援 STA 模型而設計的進程內物件。 COM 無法允許用戶端線程直接存取物件的介面,因為對象並非針對這類並行存取而設計。 相反地,COM 必須確保呼叫會同步處理,而且只會由與 “contains” 物件的 STA 相關聯的線程進行。 儘管增加了複雜性,但用戶端與程式內對象之間仍允許線程模型互操作性的所有組合。

跨進程 (以 EXE 為基礎的) 伺服器中的線程模型

以下是三種跨進程伺服器類別,不論該用戶端所使用的線程模型為何,每個類別都可供任何 COM 用戶端使用:

  1. STA 模型伺服器:

    伺服器會在一或多個STA中執行 COM 工作。 傳入呼叫會由 COM 同步處理,並由與物件建立所在的 STA 相關聯的線程傳遞。 Class-Factory 方法呼叫是由與註冊 Class Factory 之 STA 相關聯的線程傳遞。 物件和 Class Factory 不需要實作同步處理。 不過,實作器必須同步存取多個STA所使用的任何全域變數。 伺服器必須在 STA 之間使用 CoMarshalInterThreadInterfaceInStreamCoGetInterfaceAndReleaseStream 來封送處理介面指標,可能來自其他伺服器。 伺服器可選擇性地在每個 STA 中實 IMessageFilter 作 ,以控制 COM 的呼叫傳遞層面。 變質案例是執行 COM 在一個 STA 中運作的單個線程模型伺服器。

  2. MTA 模型伺服器:

    伺服器會在一或多個線程中執行 COM 工作,而這些線程全都屬於 MTA。 呼叫不會由 COM 同步處理。 COM 會在伺服器進程中建立線程集區,而用戶端呼叫則由這些線程的任何一個傳遞。 線程不需要擷取和分派訊息。 物件和 Class Factory 必須實作同步處理。 伺服器不需要封送處理線程之間的介面指標。

  3. 混合模型伺服器:

    如需詳細資訊,請參閱本文中標題為「混合線程模型」的章節。

用戶端中的線程模型

用戶端有三種類別:

  1. STA 模型用戶端:

    用戶端會在與一或多個STA相關聯的線程中執行 COM 工作。 用戶端必須使用 CoMarshalInterThreadInterfaceInStreamCoGetInterfaceAndReleaseStream 來封送處理 STA 之間的介面指標。 變質案例是單個線程模型用戶端,可讓 COM 在一個 STA 中運作。 用戶端的線程在進行傳出呼叫時,會輸入 COM 提供的訊息迴圈。 用戶端可以使用 IMessageFilter 來管理視窗訊息的回呼和處理,同時等候中斷通話和其他並行問題。

  2. MTA 模型用戶端:

    用戶端會在一或多個線程中執行 COM 工作,而這些線程全都屬於 MTA。 用戶端不需要在其線程之間封送處理介面指標。 用戶端無法使用 IMessageFilter。 客戶端的線程會在對跨進程對象進行 COM 呼叫時暫停,並在呼叫傳回時繼續。 來電會在 COM 建立和 Managed 線程上送達。

  3. 混合模型用戶端:

    如需詳細資訊,請參閱本文中標題為「混合線程模型」的章節。

程式內 (DLL 型) 伺服器中的線程模型

進程內伺服器有四種類別,不論該用戶端所使用的線程模型為何,每個類別都可供任何 COM 用戶端使用。 不過,進程內伺服器必須為任何自定義 (非系統定義) 介面提供封送處理程式代碼,如果這些介面是為了支持線程模型互操作性,因為這通常需要 COM 封送處理用戶端租使用者之間的介面。 這四個類別包括:

  1. 支援單個線程 (「main」 STA 的進程內伺服器) - 無 ThreadingModel 值:

    此伺服器所提供的物件預期會由建立它的相同用戶端 STA 存取。 此外,伺服器預期其所有進入點,例如 DllGetClassObjectDllCanUnloadNow,以及與主要 STA) 相關聯的線程 (存取全域數據。 在 COM 中導入多線程之前存在的伺服器屬於此類別。 這些伺服器並非設計成由多個線程存取,因此 COM 會在進程的主要 STA 中建立伺服器所提供的所有物件,而對對對象的呼叫是由與主要 STA 相關聯的線程所傳遞。 其他用戶端住宅可透過 Proxy 存取物件。 來自另一個 Apartment 的呼叫會從 Proxy 移至主要 STA 中的存根, (線程間封送處理) ,然後傳送至 物件。 此封送處理可讓 COM 同步處理對 物件的呼叫,而且呼叫是由建立物件的 STA 傳遞。 線程間封送處理相對於直接呼叫的速度很慢,因此建議重寫這些伺服器,以支援多個STA (類別 2) 。

  2. 支援單個線程 Apartment 模型的行程內伺服器, (多個 STA) - 標示為 ThreadingModel=Apartment

    此伺服器所提供的物件預期會由建立它的相同用戶端 STA 存取。 因此,它類似於單個線程進程內伺服器所提供的物件。 不過,此伺服器提供的物件可以在進程的多個 STA 中建立,因此伺服器必須設計其進入點,例如 DllGetClassObjectDllCanUnloadNow,以及用於多線程的全域數據。 例如,如果行程的兩個 STA 同時建立同進程物件的兩個實例, DllGetClassObject 則兩個 STA 可以同時呼叫 。 同樣地, DllCanUnloadNow 必須撰寫 ,這樣伺服器才能在程式代碼仍在伺服器中執行時受到保護,以免被卸除。

    如果伺服器只提供一個 Class Factory 實例來建立所有物件,則類別處理站實作也必須設計成多線程使用,因為它是由多個用戶端 STA 存取。 如果每次呼叫 時,伺服器都會建立 Class DllGetClassObject Factory 的新實例,則 Class Factory 不需要具備線程安全。 不過,實作器必須同步存取任何全域變數。

    類別處理站所建立的 COM 物件不需要具備線程安全。 不過,實作器必須同步全域變數的存取。 線程建立之後,一律會透過該線程存取物件,而且 COM會同步處理對物件的所有呼叫。 與建立物件的 STA 不同的用戶端 Apartment 必須透過 Proxy 存取物件。 當用戶端封送處理其住宅之間的介面時,就會建立這些 Proxy。

    任何透過其 Class Factory 建立 STA 物件的客戶端都會取得物件的直接指標。 這與單個線程進程內物件不同,其中只有用戶端的主要 STA 會取得物件的直接指標,而建立物件的所有其他 STA 會透過 Proxy 取得物件的存取權。 由於線程間封送處理相對於直接呼叫的速度很慢,因此可以藉由將單個線程進程內伺服器變更為支援多個 STA 來改善速度。

  3. 只支援 MTA 的行程內伺服器 - 標示為 ThreadingModel=Free

    此伺服器提供的物件僅適用於 MTA。 它會實作自己的同步處理,並同時由多個用戶端線程存取。 此伺服器的行為可能與 STA 模型不相容。 (例如,藉由使用 Windows 消息佇列來中斷 STA.) 的訊息幫浦,此外,藉由將對象的線程模型標示為「可用」,對象的實作者會指出下列內容:此物件可以從任何用戶端線程呼叫,但此物件也可以直接 (傳遞介面指標,而不需要將) 封送處理至它所建立的任何線程,而且這些線程可以透過這些指標進行呼叫。 因此,如果用戶端將介面指標傳遞給用戶端實作的物件, (例如接收) 到這個物件,它可以選擇從它所建立的任何線程透過這個介面指標回呼。 如果用戶端是 STA,則來自線程的直接呼叫,與建立接收對象的線程不同,將會發生錯誤 (如上述 2) 所示。 因此,COM 一律確保與 STA 相關聯之線程中的用戶端只能透過 Proxy 存取這種同進程內物件。 此外,這些物件不應該與自由線程封送處理器匯總,因為這可讓它們直接在 STA 線程上執行。

  4. 支援 Apartment 模型與自由線程的行程內伺服器 - 標示為 ThreadingModel=Both

    此伺服器提供的物件會實作自己的同步處理,並由多個用戶端 Apartment 同時存取。 此外,在 STA 或客戶端進程的 MTA 中,會直接建立和使用這個物件,而不是透過 Proxy。 因為這個物件是直接在 STA 中使用,所以伺服器必須在線程之間封送處理物件的介面,可能是來自其他伺服器的介面,因此保證它能以適合線程的方式存取任何物件。 此外,藉由將對象的線程模型標示為 「Both」,對象的實作者會指出下列內容:此物件可以從任何用戶端線程呼叫,但從這個物件到用戶端的任何回呼只會在物件接收回呼物件介面指標的 Apartment 上完成。 COM 允許直接在 STA 以及客戶端進程的 MTA 中建立這類物件。

    由於任何建立這類物件的 Apartment 一律會取得直接指標,而不是 Proxy 指標, ThreadingModel "Both" 因此物件會在 STA 中載入時,提供物件的 ThreadingModel "Free" 效能改善。

    ThreadingModel "Both"因為物件也專為 MTA 存取而設計, (在內部) 是安全線程,所以可以使用 所CoCreateFreeThreadedMarshaler提供的封送處理器匯總來加速效能。 這個系統提供的對象會匯總到任何呼叫的物件,而自定義會將物件的直接指標封送處理至進程中的所有 Apartment。 不論是 STA 或 MTA,任何 Apartment 中的用戶端都可以直接存取物件,而不是透過 Proxy 存取物件。 例如,STA 模型用戶端會在 STA1 中建立進程內物件,並將物件封送處理至 STA2。 如果物件未使用自由線程封送處理器進行匯總,STA2 會透過 Proxy 取得物件的存取權。 如果是的話,自由線程封送處理器會提供 STA2 物件的直接指標

    注意事項

    使用自由線程封送處理器匯總時,必須小心。 例如,假設標示為 ThreadingModel "Both" (的物件,以及使用自由線程封送處理器匯總的物件) 具有數據成員,該數據成員是另一個物件的介面指標,其 ThreadingModel 為 “Apartment”。 然後假設 STA 會建立第一個物件,而在建立期間,第一個物件會建立第二個物件。 根據上述所討論的規則,第一個對象現在會保留第二個物件的直接指標。 現在假設 STA 會將介面指標封送處理至另一個 Apartment 的第一個物件。 因為第一個物件會使用自由線程封送處理器匯總,所以會將第一個物件的直接指標提供給第二個 Apartment。 如果第二個 Apartment 接著透過這個指標呼叫 ,而且如果此呼叫導致第一個物件透過介面指標呼叫第二個物件,則會發生錯誤,因為第二個物件不是直接從第二個 Apartment 呼叫。 如果第一個物件持有第二個物件的 Proxy 指標,而不是直接指標,這會導致不同的錯誤。 系統 Proxy 也是與一個 Apartment 相關聯的 COM 物件。 他們會追蹤其 Apartment,以避免某些迴圈。 因此,在與執行物件之線程不同的 Apartment 相關聯的 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 之進程內伺服器的精確定義:

  1. 如果先前未將包含同進程伺服器類別的 DLL 載入 (對應至作業系統載入器) 的進程位址空間,則會執行該作業,且 COM 會取得 DLL 所導出之函式的 DLLGetClassObject 位址。 如果先前已由與任何 Apartment 相關聯的線程載入 DLL,則會略過此階段。

  2. COM 會使用線程 (,或在 MTA 的情況下,使用與「載入」Apartment 相關聯的其中一個線程) 呼叫 DllGetClassObject DLL 所導出的函式,要求需要之類別的 CLSID。 接著,傳回的 Factory 物件會用來建立 類別對象的實例。

    第二個步驟 (COM 的 DllGetClassObject 呼叫) 會在每次用戶端呼叫 CoGetClassObject/CoCreateIntance[Ex]時發生,即使是從相同 Apartment 內也一樣。 換句話說, DllGetClassObject 可能會由與相同 Apartment 相關聯的線程呼叫多次;這完全取決於該 Apartment 中有多少用戶端嘗試取得該類別的 Class Factory 物件存取權。

類別實作的作者,最後,指定類別集的 DLL 套件作者可以完全自由地決定要傳回哪個 Factory 對象來 DllGetClassObject 回應函數調用。 DLL 套件的作者具有最終的假設,因為單一 DLL 範圍 DllGetClassObject() 進入點的程式代碼「後方」是決定該怎麼做的第一個且可能最終許可權。 三種典型可能性如下:

  1. DllGetClassObject 會針對每個呼叫的線程 (傳回唯一的 Class Factory 物件,這表示每個 STA 有一個 Class Factory 物件,而且 MTA) 內可能有多個類別處理站。

  2. DllGetClassObject 不論呼叫線程的身分識別或與呼叫線程相關聯的 Apartment 類型為何,一律會傳回相同的 Class Factory 物件。

  3. DllGetClassObject 在 STA 和 MTA) 中,每個呼叫 apartment (每個 Apartment 都會傳回唯一的 Class Factory 物件。

呼叫 和傳回 (的 class Factory 物件之間 DllGetClassObject 還有其他可能的關聯性,例如每次呼叫 DllGetClassObject) 的新 Class Factory 物件,但它們目前看起來並不實用。

In-Proc 伺服器的用戶端和物件線程模型摘要

下表摘要說明當用戶端線程第一次呼叫 CoGetClassObject 實作為進程內伺服器的類別時,不同線程模型之間的互動。

用戶端/線程的種類:

  • client 在與“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 檔。