组件对象模型

概要

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鼓勵使用基於XML的SOAP訊息機制。COM非常類似其他軟體元件介面技術，如CORBA與JavaBeans，它們各自有其優點與弱點。
與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的理論基礎。
Windows作業系統提供了三種處理程序間的通訊機制：剪貼簿、DDE與OLE。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年，為應對CORBA，DCOM隨Windows NT 4 Option Pack發布。
1999年，Windows 2000發布了COM+，關注MTS，並放棄了DCOM這個名稱。

相關技術

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

COM+

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

.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 Basic、Delphi語言、Python以及Windows平台上的幾種手稿語言。它們都是通過介面的方法來存取元件。

介面

所有COM元件都實現了IUnknown介面，該介面暴露了參照計數實現的物件生命期管理與類型轉換，以存取不同的預定義介面。 
IUnknown介面以及基於IUnknown的客製化介面包括一個指向虛擬函式表的指標，虛擬函式表中包含若干函式指標，分別指向介面所聲明的函式實現。對於處理程序內的COM元件呼叫，其效率等同於C++的虛擬函式呼叫。
除了基於IUnknown的客製化介面，COM也支援繼承自IDispatch的dispatch介面，從而支援了用於OLE自動化的晚繫結。不能存取客製化介面的程式語言（例如VBS）可以通過dispatch介面存取COM元件。
Windows API提供了C語言定義COM介面的方法：
#include <objbase.h>#undef  INTERFACE#define INTERFACE   IClassFactoryDECLARE_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類型的元資料。這些類型採用微軟介面定義語言（MSIDL/IDL）描述。
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，以供其他語言編譯器與執行時環境使用，如VB、Delphi、.NET等生成語言相關表示COM類型的結構。C++把TLB轉回到IDL表示。

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 = NULLHRESULT hr = CoCreateInstance(CLSID_SomeClass, NULL, CLSCTX_ALL,
                              IID_ISomeInterface, (void**)&interface_ptr);
在這個例子中，使用COM子系統取得指向ISomeInterface介面的實現物件的指標，用CLSID_SomeClass指示用這個特定的coclass。

程式設計

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（RegFree COM）是Windows XP引入的技術，允許COM元件不在登錄檔中存期啟用的元資料與類ID（CLSID），而是在實現類的assembly manifest或者儲存在可執行檔的資源中或元件安裝時的單獨檔案中。這使得同一元件的不同版本可以安裝在不同目錄下，用其各自的manifest描述，直接複製安裝。這種技術有限支援EXE COM伺服器且不能用於系統範圍元件如MDAC、MSXML、DirectX或Internet Explorer。
應用程式裝入時，Windows裝入器搜尋manifest。如果存在，裝入器從它增加資訊到啟用上下文。COM類工廠試圖實體化一個類時，啟用上下文首先檢查這個CLSID的實現是否可以找到。僅當尋找失敗時，才掃描Windows登錄檔。

處理程序與網路透明

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

訊息泵

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

DLL地獄

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