元件物件模型

元件物件模型(英語:Component Object Model,縮寫COM)是微軟的一套軟件元件二進制介面標準。這使得跨程式語言的行程間通訊、動態物件建立成為可能。COM是多項微軟技術與框架的基礎,包括OLEOLE自動化英語OLE AutomationActiveXCOM+DCOMWindows shellDirectXWindows Runtime。COM與實作語言種類無關,如此使用它實作的物件可用在不同於開發它的環境,甚至跨越機器邊界。對製作良好的物件,COM使物件得以重複使用,而無須知道其內部實作,因為它強制實作者提供與實作分離、確切定義的介面。各語言不同的儲存組態語意使元件物件模型用物件參照計數(Reference counting)管理其自身的產生與銷毀。不同介面間型別轉換的鑄型用 QueryInterface 方法。

COM」的各地常用名稱
中國大陸組件對象模型[1]
臺灣元件物件模型[2]

概要

編輯

COM的核心是一組元件物件間互動的規範,定義了元件物件如何與其使用者通過二進制介面標準進行互動,COM的介面是元件的類型紐帶。

除了規範之外,COM還是一個稱為「COM媒體櫃」的實現,包括若干API函數,用於COM程式的建立與使用。

COM還提供定位服務的實現,可以根據Windows系統登錄檔,從一個類標識(CLSID)來確定元件的位置。

COM採用自己的IDL來描述元件的介面(interface),支援多介面,解決版本相容問題。COM為所有元件定義了一個共同的父介面IUnknown。GUID 是一個 128 位整數(16 位元組),COM將其用於電腦和網絡的唯一識別碼。

除了基本規範和系統實現之外,COM的構成還包括永久儲存、綽號(moniker智能命名/標記)和統一數據轉移(UDT = Uniform Data Transfer)三個核心的作業系統部件。

COM實質上是一種語言無關的物件實現方式,這使其可以在建立環境不同的場合、甚至跨電腦的分佈環境下被復用。COM允許復用這些物件,而不必知道物件內部是如何實現,因為元件實現者必須提供良好定義的介面從而封鎖實現細節。通過參照計數,元件物件自己負責動態建立與銷毀,從而封鎖了不同程式語言之間的主記憶體分配語意差異。在COM介面之間的類型轉換通過QueryInterface方法。

對於某些應用程式來說,COM已經部分被.NET框架取代。COM通過Windows Communication Foundation(WCF)支援Web Service。COM物件通過.NET COM Interop可以被所有.NET語言使用。網絡化的DCOM使用二進制私有格式,而WCF鼓勵使用基於XMLSOAP訊息機制。COM非常類似其他軟件元件介面技術,如CORBAJavaBeans,它們各自有其優點與弱點。

與C++不同,COM提供了一個穩定的套用二進制介面(ABI),不隨編譯器版本而改變 。

歷史

編輯

早在1988年,微軟的Anthony Williams的論文「Object Architecture: Dealing with the Unknown or Type Safety in a Dynamically Extensible Class」以及1990年的「On Inheritance: What It Means and How To Use it」論文奠定了COM的理論基礎。[3]

Windows作業系統提供了三種處理程序間的通訊機制:剪貼簿DDEOLE。OLE原名是物件連結與嵌入(Object Linking and Embedding),OLE可以說是DDE的改良版。1992年,OLE 1.0版隨Windows 3.1作業系統發佈,提供複合文件(compound document)處理,但它過於複雜,Brockschmidt, Kraig的「Inside OLE」一書中提到,必須經過六個月的心靈混沌期,才能了解OLE是什麼。1993年,COM架構隨OLE 2.0第一次公開發布。在微軟Office套件中,COM取代了OLE。這成為COM技術戰勝Windows 95團隊開發的其他物件技術的關鍵因素。

1996年,為應對CORBADCOMWindows NT 4 Option Pack發佈。

1999年,Windows 2000發佈了COM+,關注MTS,並放棄了DCOM這個名稱。

COM元件類型

編輯

COM是基於元件物件方式概念來設計的,在基礎中,至少要讓每個元件都可以支援二個功能:

  • 查詢元件中有哪些介面
  • 讓元件做自我生命管理,此概念的實踐即為參照計數(Reference Counting)

這二個功能即為COM的根:IUnknown介面所提供的IUnknown::QueryInterface()IUnknown::AddRef()IUnknown::Release()三個方法的由來。所有的COM元件都要實作IUnknown,表示每個COM元件都有相同的能力。

只由COM衍生實作出來的元件,稱為純COM元件

但在Windows持續發展時,Visual Basic 4.0開始支援OCX,也就是OLE Custom Control,這讓微軟開始思考要如何讓COM元件可以跨語言支援,在這樣的要求下,必須要提供一個一致的介面,以及提供一組可以呼叫介面內方法的能力,由於純COM元件只能夠支援C/C++的直接存取,為了要達到跨語言的能力,在COM中必須要支援在外部呼叫內部方法的機能,這個機能造就了Invoke()方法,另外為了跨語言的支援,COM應該要提供簡單的元件存取識別方式,這也就是會有GetIDsOfNames()的原因,將這些方法組合起來,定義出的必要介面,稱為IDispatch介面,所有實作此介面的,都可以支援跨語言的支援。

微軟將實作此介面的元件都稱為自動化(Automation)元件。

相關技術

編輯

COM曾是Windows平台下主要的軟件開發平台,並且影響至其他許多相關軟件技術。

COM+是微軟Windows 2000中,Microsoft Transaction Server的強化實作版本,除了提供基本的元件交易支援外,還提供了鬆散藕合式事件(loosely-coupled events)與物件共用池(object pooling)等應用程式伺服器的能力,成為Windows 2000開始在微軟平台上主要的應用程式伺服器平台,目前.NET Framework也提供了System.EnterpriseServices命名空間以支援COM+。

Distributed COM

編輯

Distributed COM是依據遠程過程調用(RPC,Remote Procedure Call)的規範發展的可以在網絡上通訊的COM元件,它將COM元件的能力擴及到網絡上,但因為網絡安全以及防火牆因素,DCOM無法廣泛的流行。

.NET Framework是新一代的Microsoft Windows應用程式開發平台。使用C#開發COM組件,首先創建類型為Class Library的專案,然後在專案的Property中進入Build頁,對「Register for COM interop」選項打勾。打開AssemblyInfo.cs檔案,設置[assembly: ComVisible(true)],這樣就可以生成.tlb檔案。源程式範例如下:

using System.Runtime.InteropServices;
 
namespace MyNameSpace
{
    //可以通过//菜单“工具/guid 生成”。
    [Guid("298D881C-E2A3-4638-B872-73EADE25511C")]
    public interface AddComInterface
    {
         [DispId(1)]  
         int iadd(int a, int b);
         [DispId(2)]
         string stradd(string strA, string strB);
     }
     [Guid("2C5B7580-4038-4d90-BABD-8B83FCE5A467")]
     [ClassInterface(ClassInterfaceType.None)]
     public class AddComService : AddComInterface
     {
         public AddComService()
         {
          }
          public int iadd(int a, int b)
          {
                int c = a + b;
                return c;
          }
          public string stradd(string strA, string strB)
          {
                 return strA+strB ;
           }
       }
}

技術細節

編輯

不同的COM元件類型用類ID(CLSID)標示,這是一種全域唯一識別碼(GUID)。每個COM元件用一個或多個介面來暴露其功能。這些介面也採用GUID唯一標識,稱為介面ID(IID)。

COM介面與幾種程式語言有語言繫結,如C語言C++Visual BasicDelphi語言Python[4][5]以及Windows平台上的幾種手稿語言。它們都是通過介面的方法來訪問元件。

介面

編輯

所有COM元件都實現了IUnknown介面,該介面暴露了參照計數實現的物件生命期管理與類型轉換,以訪問不同的預定義介面。

IUnknown介面以及基於IUnknown的客製化介面包括一個指向虛擬函式表英語virtual method table的指標,虛擬函式表中包含若干函數指標,分別指向介面所聲明的函數實現。對於處理程序內的COM元件呼叫,其效率等同於C++的虛擬函式呼叫。

除了基於IUnknown的客製化介面,COM也支援繼承自IDispatch的dispatch介面,從而支援了用於OLE自動化英語OLE Automation晚繫結英語late binding。不能訪問客製化介面的程式語言(例如VBS)可以通過dispatch介面訪問COM元件。

Windows API提供了C語言定義COM介面的方法:

#include <objbase.h>
#undef  INTERFACE
#define INTERFACE   IClassFactory

DECLARE_INTERFACE_(IClassFactory, IUnknown)
{
                // *** IUnknown methods ***
                STDMETHOD(QueryInterface) (THIS_
                                           REFIID riid,
                                           LPVOID FAR* ppvObj) PURE;
                STDMETHOD_(ULONG,AddRef) (THIS) PURE;
                STDMETHOD_(ULONG,Release) (THIS) PURE;
  
                // *** IClassFactory methods ***
                STDMETHOD(CreateInstance) (THIS_
                                          LPUNKNOWN pUnkOuter,
                                          REFIID riid,
                                          LPVOID FAR* ppvObject) PURE;
};
  
 //      等效的C++例子:
  
            struct FAR IClassFactory : public IUnknown
            {
                virtual HRESULT STDMETHODCALLTYPE QueryInterface(
                                                    IID FAR& riid,
                                                    LPVOID FAR* ppvObj) = 0;
                virtual HRESULT STDMETHODCALLTYPE AddRef(void) = 0;
                virtual HRESULT STDMETHODCALLTYPE Release(void) = 0;
                virtual HRESULT STDMETHODCALLTYPE CreateInstance(
                                                LPUNKNOWN pUnkOuter,
                                                IID FAR& riid,
                                                LPVOID FAR* ppvObject) = 0;
            };
 
 //      C语言宏扩展后是这样的:
  
            typedef struct IClassFactory
            {
                const struct IClassFactoryVtbl FAR* lpVtbl;
            } IClassFactory;
  
            typedef struct IClassFactoryVtbl IClassFactoryVtbl;
  
            struct IClassFactoryVtbl
            {
                HRESULT (STDMETHODCALLTYPE * QueryInterface) (
                                                    IClassFactory FAR* This,
                                                    IID FAR* riid,
                                                    LPVOID FAR* ppvObj) ;
                HRESULT (STDMETHODCALLTYPE * AddRef) (IClassFactory FAR* This) ;
                HRESULT (STDMETHODCALLTYPE * Release) (IClassFactory FAR* This) ;
                HRESULT (STDMETHODCALLTYPE * CreateInstance) (
                                                    IClassFactory FAR* This,
                                                    LPUNKNOWN pUnkOuter,
                                                    IID FAR* riid,
                                                    LPVOID FAR* ppvObject);
                HRESULT (STDMETHODCALLTYPE * LockServer) (
                                                    IClassFactory FAR* This,
                                                    BOOL fLock);
            };

COM類(coclass)是一個或多個介面的具體實現,它很類似物件導向程式語言中的類。類的GUID標識被稱作類ID(CLSID);或者programmatic identifier字串(progid),因為VBS等手稿語言不能使用GUID,只能用字串尋找、使用COM元件。

COM物件不能被直接訪問,只能通過COM介面來訪問物件。COM也支援同一個介面的多個實現,因此客戶程式執行時可以選擇實例化介面的哪個實現。

介面定義語言與類型媒體櫃

編輯

類型媒體櫃(type library)包含着COM類型的元數據。這些類型採用微軟介面定義語言(MIDL)描述。

IDL檔案定義了類、介面、結構、列舉與其他使用者定義類型。IDL類似於C++的聲明,使用了一些額外的關鍵字如interface、library等。IDL還支援在聲明前給出方括號內容(bracketed attribute)以提供額外資訊,如介面的GUID、指標參數與長度域之間的關係等。

MIDL編譯器用來編譯IDL檔案,產生編譯器獨立的標頭檔。標頭檔包含了IDL檔案中聲明的介面對應的結構定義。結構只包含一項成員,即指向在介面中聲明函數的地址表的指標(vtbl),以模仿C++對虛擬函式的實現。標頭檔還包含了類與介面等的GUID的常數的定義。MIDL編譯器也可以產生C++原始檔,包含代理模組(proxy module),用以把COM呼叫轉為遠端程序呼叫,以支援跨處理程序的DCOM通訊。

IDL檔案也可以被MIDL編譯器生成類型媒體櫃(TLB)檔案.TLB,以供其他語言編譯器與執行時環境使用,如VBDelphi.NET等生成語言相關表示COM類型的結構。C++把TLB轉回到IDL表示。

#import 類型資訊

編輯

使用C++的預編譯directive#import ,可以裝入如下格式的類型資訊:

  • 包含類型媒體櫃的檔案,如.olb、.tlb、.dll等;
  • progid
  • libid
  • exe檔案
  • dll檔案包含着類別館資源(如.ocx)
  • 複合文件包含了類別館
  • 其他可以被LoadTypeLib函數理解的檔案

#import建立兩個標頭檔以用C++原始碼形式恢復類型媒體櫃資訊:

  • 類型媒體櫃主標頭檔(.TLH):類似於MIDL編譯器產生的標頭檔,還有一些額外的程式碼與數據;
  • 類型媒體櫃次標頭檔(.TLI):編譯器產生的成員函數。該檔案被包含在主標頭檔中。

兩個檔案被放在輸出目錄中。編譯器在現場就地#include主標頭檔。

類型媒體櫃主標頭檔(.TLH)包含七部分:

  • 頭部常規程式碼
  • 將要用到的結構的前向參照與typedef
  • 智能指標聲明:使用宏陳述式_COM_SMARTPTR_TYPEDEF建立了COM介面的typedef,實際上是_com_ptr_t的範本特化。
  • Typeinfo聲明:類別定義、ITypeLib:GetTypeInfo返回的其他Typeinfo項
  • 可選的舊格式的GUID常數定義,形如CLSID_CoClass、IID_Interface
  • #include類型媒體櫃次標頭檔
  • 尾部常規程式碼:#pragma pack(pop)

從第2至第6部分都包含在命名空間中,其名字在最初的IDL檔案的library陳述式中給出。改名字在#import陳述式中可用內容no_namespace抑制掉;也可用rename_namespace內容更名。

COM作為物件框架

編輯

COM是一個執行時框架,類型必須在執行時單獨地標識並可指定。為此,使用GUID,每個COM類型被指定了它自己的GUID用於執行時標識。這也解決了C/C++語言的名字修飾導致的連結相容性問題。

為了使COM類型資訊在編譯時與執行時都可以訪問,COM使用類型媒體櫃。這使得COM成為物件互動的動態框架。

考慮下述用IDL定義coclass的例子:

coclass SomeClass {
  [default] interface ISomeInterface;
};

上述程式碼框架聲明了一個COM類,稱為SomeClass,實現了介面ISomeInterface

這在概念是等價於下述C++類別:

class SomeClass : public ISomeInterface {
  ...
  ...
};

其中ISomeInterface是一個C++虛基礎類別

包含COM介面與類的IDL檔案被編譯為類型媒體櫃(TLB)檔案。客戶程式可以在執行時分析類型媒體櫃檔案,以確定物件支援哪些介面,然後呼叫物件的介面方法。

C/C++程式以類ID(CLSID)與介面ID(IID)作為參數,用CoCreateInstance函數實例化COM物件。SomeClass的實例化程式碼如下:

ISomeInterface* interface_ptr = NULL
HRESULT hr = CoCreateInstance(CLSID_SomeClass, NULL, CLSCTX_ALL,
                              IID_ISomeInterface, (void**)&interface_ptr);

在這個例子中,使用COM子系統取得指向ISomeInterface介面的實現物件的指標,用CLSID_SomeClass指示用這個特定的coclass。

參照計數

編輯

所有COM物件採用參照計數管理物件的生命期。客戶程式通過所有COM物件都要強制實現的IUnknown介面的AddRef與Release方法來控制參照計數。當參照計數降到0時,COM物件自己負責釋放主記憶體。即對動態分配主記憶體建立的COM物件,其Release函數內部參照計數降為0時,就釋放自身所佔的動態分配主記憶體。有的COM物件(如IClassFactory)往往是靜態物件,Release函數內部參照計數降為0時不需做額外的操作。

特定語言(例如Visual Basic)提供了自動參照計數,所以COM物件開發者在原始碼中不需要顯式維護任何內部的參照計數。C/C++編程者或者執行顯式的參照計數,或者使用智能指標(如MFC提供的CComPtr)自動管理參照計數[需要解釋]

下述是如何呼叫COM物件的AddRef與Release的指引:

  • 函數、方法返回介面的參照(通過返回值或者"out"參數),應當在返回前增加被返回的物件的參照計數。
  • 介面指標被覆蓋或超出作用域之前,必須呼叫介面指標的Release方法。
  • 如果一個介面參照指標被複製,必須呼叫該指標的AddRef方法。
  • AddRef與Release必須在被參照的相關介面上呼叫。因為一個COM物件可能實現了逐個介面上的參照計數,使得僅在相關介面上內部分配資源。

不向遠端物件發出參照計數的呼叫。代理模組保持着遠端物件的一個參照,並維持着它自己的本機參照計數。

為簡化COM開發,引入了活動範本媒體櫃(Active Template Library,ATL)用於C++開發。ATL提供了更高層次的COM開發範式。ATL也有益於COM客戶應用程式開發擺脫直接維護參照計數,而是用智能指標物件。

其他能直接支援COM的媒體櫃與語言還包括MFC Visual C++編譯器的COM支援[6]VBScriptVisual BasicECMAScriptJavaScript)和Borland Delphi等。

程式設計

編輯

COM是一個語言獨立的二進制標準,任何能夠理解與實現COM的二進制定義的資料類型與介面的語言都可以開發COM元件。

COM實現負責進入、離開COM環境,實例化與參照計數COM物件,查詢物件支援的介面,以及錯誤處理。

Microsoft Visual C++編譯器支援對C++語言的擴充:稱作C++ Attributes[7]這些擴充被設計用於簡化COM開發,去除實現COM伺服器時大量臃腫的程式碼。[8]

使用登錄檔

編輯

在Windows作業系統中,COM類、介面、類型媒體櫃都會根據其GUID登記到Windows登錄檔。HKEY_CLASSES_ROOT\CLSID下是COM類;HKEY_CLASSES_ROOT\Interface下是介面。COM類型媒體櫃註冊在每個COM物件的本機媒體櫃條目下或者遠端服務的網絡位置處。

不使用登錄檔的COM

編輯

不使用登錄檔的COM(RegFree COM)是Windows XP引入的技術,允許COM元件不在登錄檔中存期啟用的元數據與類ID(CLSID),而是在實現類的assembly manifest英語Manifest (CLI)或者儲存在可執行檔案的資源中或元件安裝時的單獨檔案中。[9]這使得同一元件的不同版本可以安裝在不同目錄下,用其各自的manifest描述,直接複製安裝英語XCOPY deployment[10]這種技術有限支援EXE COM伺服器[11]且不能用於系統範圍元件如MDACMSXMLDirectXInternet Explorer

應用程式裝入時,Windows裝入器搜尋manifest。[12]如果存在,裝入器從它增加資訊到啟用上下文。[10]COM類工廠試圖實例化一個類時,啟用上下文首先檢查這個CLSID的實現是否可以找到。僅當尋找失敗時,才掃描Windows登錄檔[10]

處理程序與網絡透明

編輯

COM物件可以透明地實例化與參照在同一處理程序、跨處理程序邊界、甚至在網上遠端(DCOM)。處理程序外或遠端物件用marshalling序列化方法呼叫與返回值。這種marshalling對使用者是不可見的,就如同訪問處理程序內的COM物件。

執行緒化與「套間」

編輯

一個處理程序載入了一個COM的DLL檔案後,該DLL可能定義並使用了一些可修改的全域變數或訪問共用資源。該處理程序內的多個執行緒如何並行訪問該DLL並保證是執行緒安全的,這就是「套間」(apartment)技術需要解決的問題。

COM物件與建立或呼叫COM物件的執行緒可以按兩種策略來實現並行安全:

  • 按照單執行緒執行方式寫COM物件的程式碼,完全不考慮並行執行問題。這樣的每個COM物件只能由一個執行緒執行,該執行緒通過Windows訊息佇列實現多執行緒訪問該COM物件被串行化從而並行安全。這種策略稱作單執行緒套間(Single-Threaded Apartment,STA)。
  • COM物件的程式碼自身實現了並行控制(通過Windows互斥原語,如互斥鎖臨界區事件訊號量等)。因此實際上多執行緒可以直接呼叫該COM物件的方法,這是並行安全的。這種策略稱作多執行緒套間(Multi-Threaded Apartment,MTA)。

COM的並行安全的具體實現,提出了套間(apartment)概念。每一種套間類型表示在一個處理程序內部是多執行緒情況下,如何同步對COM物件的呼叫。套間是一個邏輯容器,收納遵循相同執行緒訪問規則的COM物件與COM執行緒(建立了COM物件的執行緒或者呼叫了COM物件的方法的執行緒)。套間本質上只是一個邏輯概念而非物理實體,沒有控制代碼類型可以參照它,更沒有可呼叫的API操縱它。套間有兩種:

  • 單執行緒套間(Single-Threaded Apartment,STA):每個處理程序可以有多個STA套間。每個STA套間只能有一個執行緒。每個STA性質的COM物件只能屬於一個STA套間。一個STA套間可以有零個或多個STA內容的COM物件,這些COM物件的方法只能由該套間的唯一執行緒執行。STA套間的執行緒可以直接呼叫該套間的COM物件的方法。如果STA套間的COM物件被套間外的執行緒或處理程序呼叫,那麼該套間的執行緒必須實現Windows訊息佇列與訊息迴圈處理機制,其他執行緒必須通過marshalling與unmarshalling機制,通過給該STA套間的執行緒傳送Windows訊息來呼叫COM物件。每個STA性質的執行緒自動形成一個STA套間,這個套間容納了該執行緒及其建立的所有STA性質COM物件。MTA性質的執行緒建立STA性質的COM物件時,系統自動把該COM物件放在default STA套間內,由該套間的STA執行緒來執行該COM物件的方法。每個處理程序至多有一個default STA套間,該套間與套間內執行緒是自動生成的。
  • 多執行緒套間(Multi-Threaded Apartment,MTA):每個處理程序至多有一個MTA套間。所有MTA性質的執行緒都屬於MTA套間。所有MTA性質的COM物件也都屬於這個MTA套間。STA性質的執行緒建立MTA性質的COM物件時,系統自動建立一些執行緒以執行這些MTA性質的COM物件,這些執行緒也屬於MTA套間,系統返回安整後的COM物件的描述給STA性質的執行緒。
  • 中立套間(Thread Neutral Apartment,NA):一個處理程序可以有一個中立套間。中立套間只包含COM物件,不包含執行緒。當STA或MTA執行緒呼叫同一處理程序的NA物件,則呼叫執行緒臨時離開它的套間並執行COM物件的程式碼,沒有任何執行緒切換。即任何執行緒都可以直接了當呼叫COM物件的方法。[13]因此NA可以認為是最佳化套間之間方法呼叫的效率。

一個COM物件只能存在於一個套間。COM物件一經建立就確定所屬套間,並且直到銷毀它一直存在於這個套間。COM物件的套間類型寫在Windows登錄檔相關條目中。

一個COM執行緒從建立到結束都屬於同一個套間。COM執行緒只有兩種套間模式:STA或MTA。[14]執行緒必須通過呼叫CoInitializeEx()函數並且設定參數為COINIT_APARTMENTTHREADED或者COINIT_MULTITHREADED,來指明該執行緒的套間模式。呼叫了CoInitializeEx()函數的執行緒即已進入套間,直到執行緒呼叫CoUninitialize()函數或者自身終止,才會離開套間。COM為每個STA的執行緒自動建立了一個隱藏視窗,其Windows class是"OleMainThreadWndClass" 。跨套間呼叫這個STA套間內的COM物件,實際上是向這個隱藏視窗傳送了一條視窗訊息,通過訊息迴圈與分派,該視窗過程收到這條視窗訊息並呼叫相應的COM物件的介面方法。

執行緒訪問屬於同一套間的COM物件,直接執行方法呼叫而不需COM設施的輔助。執行緒跨套間邊界去呼叫COM物件,傳遞的指標需要marshalling。如果通過標準的COM的API來呼叫,可以自動完成安整。例如,把一個COM介面指標作為參數傳遞給另外一個套間的COM物件的proxy的情形。但如果軟件編程者跨套間傳遞介面指標而沒有使用標準COM機制,就需要手工完成安整(通過CoMarshalInterThreadInterfaceInStream函數)與反安整(通過CoGetInterfaceAndReleaseStream函數取得COM介面的proxy)。例如,把COM介面指標作為執行緒啟動時的參數傳遞的情形。

跨處理程序的呼叫COM物件類似於同一處理程序內跨套間的呼叫COM物件。

COM物件coclass在登錄檔表示中的子鍵InProcServer32下的條目中ThreadingModel給出:

ThreadingModel的值 描述
Legacy STA(ThreadingModel=Single或空 ) 該COM物件屬於處理程序的第一個STA執行緒,通常是UI介面的執行緒。這是在過去單核CPU時代沒有遺留下來的。
單執行緒套間[15]STA),(ThreadingModel=Apartment 一個單獨的執行緒專門用於執行COM物件的方法。如果是STA的COM執行緒建立了STA的COM物件,這個COM物件的方法就由該執行緒執行,該執行緒呼叫該COM物件是直接呼叫。如果MTA的COM執行緒建立了STA的COM物件,系統在當前處理程序內自動建立一個default STA執行緒來執行該STA的COM物件的方法,並把COM物件的proxy返回該MTA的執行緒。COM物件所在STA套間之外的執行緒呼叫該COM物件的方法,需要對COM物件的指標先做marshalling再由作業系統自動排隊(通過該COM物件被呼叫方法所在的執行緒的標準的Microsoft Windows的訊息迴圈)。這提供了自動同步以確保物件的方法每次呼叫執行完畢後才能啟動方法的新的呼叫。開發者不需要擔心執行緒加鎖(locking)或競態條件。如果跨套間呼叫STA的COM物件,該物件所在STA的執行緒必須提供執行緒訊息迴圈處理機制。
多執行緒套間[16]MTA),(ThreadingModel=Free COM執行時不提供同步,多個MTA執行緒可以同時呼叫同一個MTA的COM物件,由各個MTA執行緒直接執行COM物件的方法,且因為在同一個MTA中因此不需要安整。COM物件需要自己實現同步控制以避免多執行緒同時訪問造成的競態條件或死結。STA的執行緒建立MTA的COM物件,系統自動建立一個或多個執行緒來執行MTA的COM物件。STA執行緒呼叫MTA的COM物件也需要marshalling,系統自動分配某個自動建立的執行緒來執行COM物件。MTA的優點是提高了並行處理效能,同時工作執行緒不需要有自己的Windows訊息迴圈
自動選擇套間[17],(ThreadingModel=Both COM物件的套間類別與建立它的執行緒的套間類別一致。這避免了很多marshalling開銷,例如一個MTA伺服器被一個STA執行緒呼叫。
Thread Neutral ApartmentNA),(ThreadingModel=Neutral 一個特殊的套間,沒有任何指定的執行緒。當STA或MTA執行緒呼叫同一處理程序的NA物件,則呼叫執行緒臨時離開它的套間並執行COM物件的程式碼,沒有任何執行緒切換。即任何執行緒都可以直接了當呼叫COM物件的方法。[13]因此NA可以認為是最佳化套間之間方法呼叫的效率。

批評

編輯

訊息泵

編輯

STA初始化時,建立一個隱藏視窗,用於apartment之間、行程間的訊息路由。該視窗必須有正常的訊息佇列泵。這種結構稱為訊息泵。早期版本的Windows,訊息泵的失敗會導致系統範圍的死結。這個問題被初始化COM的Windows API複雜化了,並會導致實現細節的泄露。

參照計數

編輯

如果多個物件是循環參照(Circular reference),則可能會導致問題。

Objects may also be left with active reference counts if the 使用COM事件池(event sink)模型,則物件可能一直保持活動的參照計數而不能被銷毀。因為傳送事件的物件必須有處理事件的物件的參照,因而物件參照計數永遠不為0.

參照迴圈可以採取下述技術來克服:

  • 帶外終止(out-of-band termination),物件暴露一個方法,該方法呼叫時迫使該物件放棄對其他物件的全部參照。
  • 身份分離(split identity)技術,一個實現暴露兩個單獨的COM物件(也稱作identity),之間保持weak reference

DLL地獄

編輯

處理程序內的COM元件是用DLL檔案實現,每個版本的DLL用CLSID登記到Windows登錄檔,因而某些情況下會發生DLL Hell效應。無需註冊的COM克服了這一問題。

元件間的約定的表示

編輯

COM元件間的約定,純粹是通過使用者與元件之間的語意保證和假設的形式來表示的。COM用類型的形式表示元件約定。但是該約定存在如下兩個關鍵問題,使得其對語意的表示並不是最佳的。

  • 約定的描述:COM沒有定義約定的交換格式,即COM規範所約定的類型定義,必須通過完全是COM之外的某種技術來進行互動。微軟定義和支援的COM交換格式有兩個——IDL(Interface Definition Language介面語言定義)和TLB(Type LiBrary類型媒體櫃),但是這兩種格式並不是同構的,其中也沒有哪種格式是權威的或標準的。
    • COM缺乏對元件依賴性的描述。因此,沒有辦法來解析COM元件(或者其約定的定義),也不能確定它所需要(依賴)的其他元件與版本。
    • COM約定的描述格式缺乏擴充性。IDL是基於文字的,極少隨元件部署,通常只有C++程式設計師才會使用。TLB在擴充性方面存在缺陷,VB不使用TLB。
  • 約定的工作方式是基於類型描述的,所採用的型別系統是C++的可移植子集。而且COM對元件的約定是物理的二進制約定,要求每個方法都具有精確的虛擬函式表偏移量、每個被傳遞的參數在堆疊規則中都有明確的偏移量、物件參照採用介面指標的明確格式、使用規定的分配器進行被呼叫這主記憶體分配。COM元件的約定的精確性,產生了高效的程式碼;但不可靠性、開發使用及擴充升級的困難與複雜性高昂。

參見

編輯

參考文獻

編輯
  1. ^ 搜索术语. Microsoft. [2015-04-22]. (原始內容存檔於2016-03-06) (中文(簡體)). 
  2. ^ 搜尋詞彙. Microsoft. [2015-04-22]. (原始內容存檔於2016-03-06) (中文(繁體)). 
  3. ^ "COM (DCOM) Team won 2011 Outstanding Technical Achievement in Microsoft". [2016-12-27]. (原始內容存檔於2017-01-18). 
  4. ^ 存档副本. [2014-06-07]. (原始內容存檔於2020-05-15). 
  5. ^ 存档副本. [2014-06-07]. (原始內容存檔於2021-04-23). 
  6. ^ Compiler COM Support. MSDN. Microsoft. [2014-06-07]. (原始內容存檔於2018-09-24). 
  7. ^ Microsoft MSDN: C++ Attributes Reference頁面存檔備份,存於互聯網檔案館
  8. ^ MSDN Magazine: C++ Attributes: Make COM Programming a Breeze with New Feature in Visual Studio .NET頁面存檔備份,存於互聯網檔案館
  9. ^ Assembly Manifests. MSDN. [2009-11-05]. (原始內容存檔於2018-05-10). 
  10. ^ 10.0 10.1 10.2 Dave Templin. Simplify App Deployment with ClickOnce and Registration-Free COM. MSDN Magazine. [2008-04-22]. (原始內容存檔於2015-04-11). 
  11. ^ How to use an out-of-process COM server without its tlb file. [2011-04-16]. (原始內容存檔於2020-08-10). 
  12. ^ Concepts of Isolated Applications and Side-by-side Assemblies. MSDN. [2009-11-05]. (原始內容存檔於2010-03-05). 
  13. ^ 13.0 13.1 Codeguru: Understanding COM Apartments頁面存檔備份,存於互聯網檔案館
  14. ^ Microsoft MSDN: Processes, Threads, and Apartments頁面存檔備份,存於互聯網檔案館
  15. ^ Microsoft MSDN: Single-Threaded Apartments頁面存檔備份,存於互聯網檔案館
  16. ^ Microsoft MSDN: Multithreaded Apartments頁面存檔備份,存於互聯網檔案館
  17. ^ Microsoft MSDN: Understanding and Using COM Threading Models頁面存檔備份,存於互聯網檔案館

外部連結

編輯