進程伺服器線程問題

同進程伺服器不會呼叫 CoInitialize、CoInitializeExOleInitialize 來標示其線程模型。 針對線程感知 DLL 型或行程內物件,您必須在登錄中設定線程模型。 當您未指定線程模型時,預設模型是單個線程每一進程。 若要指定模型,您可以將 ThreadingModel 值新增登錄中的 InprocServer32 機碼。

支援類別物件具現化的 DLL 必須實作和導出 DllGetClassObject DllCanUnloadNow 函式。 當用戶端想要 DLL 支援的類別實例時,對 CoGetClassObject 的呼叫(直接或透過呼叫 CoCreateInstance)呼叫 DllGetClassObject,以在 DLL 中實作物件時取得其類別物件的指標。 因此,DllGetClassObject 應該能夠放棄多個類別物件或單一安全線程物件(基本上只是在其內部參考計數上使用 InterlockedIncrement InterlockedDecrement/)。

正如其名稱所暗示, 會呼叫 DllCanUnloadNow 來判斷實作它的 DLL 是否正在使用中,讓呼叫端在不是時安全地卸除它。 從任何線程呼叫 CoFreeUnusedLibraries 時,一律會透過主 Apartment 的線程路由呼叫 DllCanUnloadNow

與其他伺服器一樣,同進程伺服器可以是單個線程、Apartment 線程或自由線程。 不論該用戶端所使用的線程模型為何,這些伺服器都可以由任何 OLE 用戶端使用。

用戶端與進程內對象之間允許線程模型互操作性的所有組合。 用戶端與使用不同線程模型之進程對象之間的互動,與用戶端與跨進程伺服器之間的互動完全相同。 若為同進程伺服器,當用戶端和同進程伺服器線程模型不同時,COM 必須在用戶端與對象之間交置本身。

當用戶端的多個線程同時呼叫支援單個線程模型的同進程物件時,COM 無法允許用戶端線程直接存取物件的介面,“物件不是針對這類存取所設計。 相反地,COM 必須確定呼叫已同步處理,而且只會由建立物件的用戶端線程進行。 因此,COM 會在用戶端的主要 Apartment 中建立 物件,並要求所有其他用戶端 Apartment 使用 Proxy 存取物件。

當用戶端中的自由線程 Apartment(多線程 Apartment 模型)建立 Apartment 線程內伺服器時,COM 會在用戶端中啟動單個線程 Apartment 模型「主機」線程。 此主機線程會建立 物件,並將介面指標封送處理回用戶端的無線程 Apartment。 同樣地,當 Apartment 模型用戶端中的單個線程 Apartment 建立自由線程同進程伺服器時,COM 會啟動自由線程主機線程(將在其中建立物件的多線程 Apartment,然後封送處理回用戶端單個線程 Apartment)。

注意

一般而言,如果您在處理中伺服器上設計自定義介面,則也應該提供封送處理程式代碼,讓 COM可以在用戶端 Apartment 之間封送處理介面。

 

COM 藉由要求從建立物件所在的相同用戶端 Apartment 存取,協助保護對單個線程 DLL 所提供的物件存取。 此外,所有 DLL 進入點(例如 DllGetClassObject 和 DllCanUnloadNow)和全域數據都應該由相同的 Apartment 存取。 COM 會在用戶端的主要 Apartment 中建立這類物件,讓主要 Apartment 直接存取物件的指標。 來自其他 Apartment 的呼叫會使用線程間封送處理,從 Proxy 移至主要 Apartment 中的存根,然後移至 物件。 這可讓 COM 同步處理對 物件的呼叫。 線程間呼叫速度很慢,因此建議重寫這些伺服器以支援多個 Apartment。

如同同一部線程同進程伺服器,Apartment 模型 DLL 所提供的對象必須由建立所在的相同用戶端 Apartment 存取。 不過,此伺服器所提供的物件可以在用戶端的多個 Apartment 中建立,因此伺服器必須實作其進入點(例如 DllGetClassObject DllCanUnloadNow),才能使用多線程。 例如,如果用戶端的兩個 Apartment 嘗試同時建立兩個同進程對象實例, 則這兩個 Apartment 可以同時呼叫 DllGetClassObjectDllCanUnloadNow 必須寫入,如此一來,當程序代碼仍在 DLL 中執行時,DLL 不會卸除。

如果 DLL 只提供類別處理站的一個實例來建立所有物件,則類別處理站實作也必須針對多線程使用而設計,因為它將由多個用戶端 Apartment 存取。 如果每次呼叫 DllGetClassObject,DLL 都會建立類別處理站的新實例,則類別處理站不需要安全線程。

類別處理站所建立的物件不需要安全線程。 線程建立之後,一律會透過該線程存取物件,而且 COM會同步處理對物件的所有呼叫。 建立此物件的用戶端 Apartment 模型 Apartment 將取得物件的直接指標。 不同於建立物件之 Apartment 的用戶端 Apartment 必須透過 Proxy 存取物件。 當用戶端封送處理其 Apartment 之間的介面時,就會建立這些 Proxy。

當進程 DLL ThreadingModel 值設定為 “Both”時,此 DLL 所提供的物件可以直接在單個線程或多線程用戶端 Apartment 中建立及使用(不含 Proxy)。 不過,它只能直接在建立所在的Apartment內使用。 若要將 物件提供給任何其他 Apartment,則必須封送處理物件。 DLL 對象必須實作自己的同步處理,而且可以同時由多個用戶端 Apartment 存取。

為了加快自由線程存取同進程 DLL 物件的效能,COM 提供 CoCreateFreeThreadedMarshaler 函式。 此函式會建立可以使用同進程伺服器物件匯總的自由線程封送處理物件。 當相同進程中的用戶端 Apartment 需要存取另一個 Apartment 中的物件時,匯總自由線程封送處理器會為用戶端提供伺服器物件的直接指標,而不是 Proxy,當用戶端將對象的介面封送處理至不同的 Apartment 時。 用戶端不需要執行任何同步處理。 這隻適用於同一個進程;標準封送處理用於傳送至另一個進程之對象的參考。

進程內 DLL 所提供的物件,僅支援自由線程處理是一個自由線程物件。 它會實作自己的同步處理,並可同時由多個用戶端線程存取。 此伺服器不會封送處理線程之間的介面,因此此伺服器只能由用戶端中的多線程 Apartment 直接建立及使用(不含 Proxy)。 建立的單線程 Apartment 會透過 Proxy 存取它。

跨 Apartment 存取介面

選擇線程模型

多線程公寓

進程、線程和 Apartment

單個線程和多線程通訊

單個線程 Apartment