元件物件模型

元件物件模型是微軟的一套軟體元件的二進位制介面標準。這使得跨程式語言的程序間通訊、動態物件建立成為可能。COM是多項微軟技術與框架的基礎，包括OLE、、ActiveX、COM+、DCOM、Windows shell、DirectX、Windows Runtime。COM與實作語言種類無關，如此使用它實作的物件可用在不同於開發它的環境，甚至跨越機器邊界。對製作良好的物件，COM使物件得以重複使用，而無須知道其內部實作，因為它強制實作者提供與實作分離、確切定義的介面。各語言不同的儲存配置語意使元件物件模型用物件參照計數(Reference counting)管理其自身的產生與銷毀。不同介面間型別轉換的鑄型用 QueryInterface 方法。概要.COM的核心是一組元件物件間互動的規範，定義了元件物件如何與其使用者透過二進位制介面標準進行互動，COM的介面是元件的型別紐帶。除了規範之外，COM還是一個稱為“COM庫”的實現，包括若干API函式，用於COM程式的建立與使用。COM還提供定位服務的實現，可以根據Windows系統登錄檔，從一個類標識來確定元件的位置。COM採用自己的IDL來描述元件的介面，COM將其用於計算機和網路的唯一識別符號。除了基本規範和系統實現之外，COM的構成還包括永久儲存、綽號三個核心的作業系統部件。COM實質上是一種語言無關的物件實現方式，這使其可以在建立環境不同的場合、甚至跨計算機的分佈環境下被複用。COM允許複用這些物件，而不必知道物件內部是如何實現，因為元件實現者必須提供良好定義的介面從而遮蔽實現細節。透過引用計數，元件物件自己負責動態建立與銷燬，從而遮蔽了不同程式語言之間的記憶體分配語義差異。在COM介面之間的型別轉換透過QueryInterface方法。對於某些應用程式來說，COM已經部分被.NET框架取代。COM透過Windows Communication Foundation支援Web Service。COM物件透過.NET COM Interop可以被所有.NET語言使用。網路化的DCOM使用二進位制私有格式，而WCF鼓勵使用基於XML的SOAP訊息機制。COM非常類似其他軟體元件介面技術，如CORBA與JavaBeans，它們各自有其優點與弱點。與C++不同，COM提供了一個穩定的應用二進位制介面，不隨編譯器版本而改變 。歷史.早在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的理論基礎。Windows作業系統提供了三種程序間的通訊機制：剪貼簿、DDE與OLE。OLE原名是物件連結與嵌入處理，但它過於複雜，Brockschmidt, Kraig的「Inside OLE」一書中提到，必須經過六個月的心靈混沌期，才能瞭解OLE是什麼。1993年，COM架構隨OLE 2.0第一次公開發布。在微軟Office套件中，COM取代了OLE。這成為COM技術戰勝Windows 95團隊開發的其他物件技術的關鍵因素。1996年，為應對CORBA，DCOM隨Windows NT 4 Option Pack釋出。1999年，Windows 2000釋出了COM+，關注MTS，並放棄了DCOM這個名稱。COM元件型別.COM是基於元件物件方式概念來設計的，在基礎中，至少要讓每個元件都可以支援二個功能：這二個功能即為COM的根：codice_1介面所提供的codice_2，codice_3及codice_4三個方法的由來。所有的COM元件都要實作codice_1，表示每個COM元件都有相同的能力。只由COM衍生實作出來的元件，稱為純COM元件。但在Windows持續發展時，Visual Basic 4.0開始支援OCX，也就是OLE Custom Control，這讓微軟開始思考要如何讓COM元件可以跨語言支援，在這樣的要求下，必須要提供一個一致的介面，以及提供一組可以呼叫介面內方法的能力，由於純COM元件只能夠支援C/C++的直接存取，為了要達到跨語言的能力，在COM中必須要支援在外部呼叫內部方法的機能，這個機能造就了codice_6方法，另外為了跨語言的支援，COM應該要提供簡單的元件存取識別方式，這也就是會有codice_7的原因，將這些方法組合起來，定義出的必要介面，稱為codice_8介面，所有實作此介面的，都可以支援跨語言的支援。微軟將實作此介面的元件都稱為自動化元件。相關技術.COM曾是Windows平臺下主要的軟體開發平臺，並且影響至其他許多相關軟體技術。COM+.COM+是微軟Windows 2000中，Microsoft Transaction Server的強化實作版本，除了提供基本的元件交易支援外，還提供了鬆散藕合式事件等應用程式伺服器的能力，成為Windows 2000開始在微軟平臺上主要的應用程式伺服器平臺，目前.NET Framework也提供了System.EnterpriseServices名稱空間以支援COM+。Distributed COM.Distributed COM是依據遠端過程呼叫的規範發展的可以在網路上通訊的COM元件，它將COM元件的能力擴及到網路上，但因為網路安全以及防火牆因素，DCOM無法廣泛的流行。.NET..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 : AddComInterfacepublic 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。COM介面與幾種程式語言有語言繫結，如C語言、C++、Visual Basic、Delphi語言、Python以及Windows平臺上的幾種指令碼語言。它們都是透過介面的方法來訪問元件。介面.所有COM元件都實現了IUnknown介面，該介面暴露了引用計數實現的物件生命期管理與型別轉換，以訪問不同的預定義介面。IUnknown介面以及基於IUnknown的定製介面包括一個指向的指標，虛擬函式表中包含若干函式指標，分別指向介面所宣告的函式實現。對於程序內的COM元件呼叫，其效率等同於C++的虛擬函式呼叫。除了基於IUnknown的定製介面，COM也支援繼承自IDispatch的dispatch介面，從而支援了用於的。不能訪問定製介面的程式語言可以透過dispatch介面訪問COM元件。Windows API提供了C語言定義COM介面的方法：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 IUnknownvirtual HRESULT STDMETHODCALLTYPE QueryInterface(IID FAR&amp; 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&amp; riid,LPVOID FAR* ppvObject) = 0;// C語言宏擴充套件後是這樣的:typedef struct IClassFactoryconst struct IClassFactoryVtbl FAR* lpVtbl;} IClassFactory;typedef struct IClassFactoryVtbl IClassFactoryVtbl;struct IClassFactoryVtblHRESULT (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類，因為VBS等指令碼語言不能使用GUID，只能用字串查詢、使用COM元件。COM物件不能被直接訪問，只能透過COM介面來訪問物件。COM也支援同一個介面的多個實現，因此客戶程式執行時可以選擇例項化介面的哪個實現。介面定義語言與型別庫.型別庫描述。IDL檔案定義了類、介面、結構、列舉與其他使用者定義型別。IDL類似於C++的宣告，使用了一些額外的關鍵字如interface、library等。IDL還支援在宣告前給出方括號屬性以提供額外資訊，如介面的GUID、指標引數與長度域之間的關係等。MIDL編譯器用來編譯IDL檔案，產生編譯器獨立的標頭檔案。標頭檔案包含了IDL檔案中宣告的介面對應的結構定義。結構只包含一項成員，即指向在介面中宣告函式的地址表的指標，用以把COM呼叫轉為遠端過程呼叫，以支援跨程序的DCOM通訊。IDL檔案也可以被MIDL編譯器生成型別庫檔案codice_9，以供其他語言編譯器與執行時環境使用，如VB、Delphi、.NET等生成語言相關表示COM型別的結構。C++把TLB轉回到IDL表示。#import 型別資訊.使用C++的預編譯directivecodice_10 ，可以裝入如下格式的型別資訊：codice_10建立兩個標頭檔案以用C++原始碼形式恢復型別庫資訊：兩個檔案被放在輸出目錄中。編譯器在現場就地codice_12主標頭檔案。型別庫主標頭檔案包含七部分：從第2至第6部分都包含在名稱空間中，其名字在最初的IDL檔案的codice_16語句中給出。改名字在codice_10語句中可用屬性no_namespace抑制掉；也可用rename_namespace屬性更名。COM作為物件框架.COM是一個執行時框架，型別必須在執行時單獨地標識並可指定。為此，使用GUID，每個COM型別被指定了它自己的GUID用於執行時標識。這也解決了C/C++語言的名字修飾導致的連結相容性問題。為了使COM型別資訊在編譯時與執行時都可以訪問，COM使用型別庫。這使得COM成為物件互動的動態框架。考慮下述用IDL定義coclass的例子：coclass SomeClass {[default] interface ISomeInterface;上述程式碼框架聲明瞭一個COM類，稱為codice_18，實現了介面codice_19。這在概念是等價於下述C++類：class SomeClass : public ISomeInterface {...其中ISomeInterface是一個C++虛基類。包含COM介面與類的IDL檔案被編譯為型別庫檔案。客戶程式可以在執行時分析型別庫檔案，以確定物件支援哪些介面，然後呼叫物件的介面方法。C/C++程式以類ID作為引數，用codice_20函式例項化COM物件。codice_18的例項化程式碼如下：ISomeInterface* interface_ptr = NULLHRESULT hr = CoCreateInstance(CLSID_SomeClass, NULL, CLSCTX_ALL,IID_ISomeInterface, (void**)&amp;interface_ptr);在這個例子中，使用COM子系統獲取指向codice_19介面的實現物件的指標，用CLSID_SomeClass指示用這個特定的coclass。引用計數.所有COM物件採用引用計數管理物件的生命期。客戶程式透過所有COM物件都要強制實現的IUnknown介面的AddRef與Release方法來控制引用計數。當引用計數降到0時，COM物件自己負責釋放記憶體。即對動態分配記憶體建立的COM物件，其Release函式內部引用計數降為0時，就釋放自身所佔的動態分配記憶體。有的COM物件往往是靜態物件，Release函式內部引用計數降為0時不需做額外的操作。特定語言自動管理引用計數。下述是如何呼叫COM物件的AddRef與Release的指引：不向遠端物件發出引用計數的呼叫。代理模組保持著遠端物件的一個引用，並維持著它自己的本地引用計數。為簡化COM開發，引入了活動模板庫用於C++開發。ATL提供了更高層次的COM開發正規化。ATL也有益於COM客戶應用程式開發擺脫直接維護引用計數，而是用智慧指標物件。其他能直接支援COM的庫與語言還包括MFC Visual C++編譯器的COM支援、VBScript、Visual Basic、ECMAScript和Borland Delphi等。程式設計.COM是一個語言獨立的二進位制標準，任何能夠理解與實現COM的二進位制定義的資料型別與介面的語言都可以開發COM元件。COM實現負責進入、離開COM環境，例項化與引用計數COM物件，查詢物件支援的介面，以及錯誤處理。Microsoft Visual C++編譯器支援對C++語言的擴充套件：稱作"C++ Attributes"。這些擴充套件被設計用於簡化COM開發，去除實現COM伺服器時大量臃腫的程式碼。使用登錄檔.在Windows作業系統中，COM類、介面、型別庫都會根據其GUID登記到Windows登錄檔。HKEY_CLASSES_ROOT\CLSID下是COM類；HKEY_CLASSES_ROOT\Interface下是介面。COM型別庫註冊在每個COM物件的本地庫條目下或者遠端服務的網路位置處。不使用登錄檔的COM.不使用登錄檔的COM，而是在實現類的或者儲存在可執行檔案的資源中或元件安裝時的單獨檔案中。這使得同一組件的不同版本可以安裝在不同目錄下，用其各自的manifest描述，直接。這種技術有限支援EXE COM伺服器且不能用於系統範圍元件如MDAC、MSXML、DirectX或Internet Explorer。應用程式裝入時，Windows裝入器搜尋manifest。如果存在，裝入器從它增加資訊到啟用上下文。COM類工廠試圖例項化一個類時，啟用上下文首先檢查這個CLSID的實現是否可以找到。僅當查詢失敗時，才掃描Windows登錄檔。程序與網路透明.COM物件可以透明地例項化與引用在同一程序、跨程序邊界、甚至在網上遠端。程序外或遠端物件用marshalling序列化方法呼叫與返回值。這種marshalling對使用者是不可見的，就如同訪問程序內的COM物件。執行緒化與“套間”.一個程序載入了一個COM的DLL檔案後，該DLL可能定義並使用了一些可修改的全域性變數或訪問共享資源。該程序內的多個執行緒如何併發訪問該DLL並保證是執行緒安全的，這就是“套間”技術需要解決的問題。COM物件與建立或呼叫COM物件的執行緒可以按兩種策略來實現併發安全：COM的併發安全的具體實現，提出了套間。套間本質上只是一個邏輯概念而非物理實體，沒有控制代碼型別可以引用它，更沒有可呼叫的API操縱它。套間有兩種：一個COM物件只能存在於一個套間。COM物件一經建立就確定所屬套間，並且直到銷燬它一直存在於這個套間。COM物件的套間型別寫在Windows登錄檔相關條目中。一個COM執行緒從建立到結束都屬於同一個套間。COM執行緒只有兩種套間模式：STA或MTA。執行緒必須透過呼叫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機制，就需要手工完成安整。例如，把COM介面指標作為執行緒啟動時的引數傳遞的情形。跨程序的呼叫COM物件類似於同一程序內跨套間的呼叫COM物件。COM物件coclass在登錄檔表示中的子鍵InProcServer32下的條目中ThreadingModel給出：批評.訊息泵.STA初始化時，建立一個隱藏視窗，用於apartment之間、程序間的訊息路由。該視窗必須有正常的訊息佇列泵。這種結構稱為訊息泵。早期版本的Windows，訊息泵的失敗會導致系統範圍的死鎖。這個問題被初始化COM的Windows API複雜化了，並會導致實現細節的洩露。引用計數.如果多個物件是迴圈參照，則可能會導致問題。Objects may also be left with active reference counts if the使用COM事件池模型，則物件可能一直保持活動的引用計數而不能被銷燬。因為傳送事件的物件必須有處理事件的物件的引用，因而物件引用計數永遠不為0.引用迴圈可以採取下述技術來克服：DLL地獄.程序內的COM元件是用DLL檔案實現，每個版本的DLL用CLSID登記到Windows登錄檔，因而某些情況下會發生DLL Hell效應。無需註冊的COM克服了這一問題。元件間的約定的表示.COM元件間的約定，純粹是透過使用者與元件之間的語義保證和假設的形式來表示的。COM用型別的形式表示元件約定。但是該約定存在如下兩個關鍵問題，使得其對語義的表示並不是最優的。