組件對象模型

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

## 概要

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元件類型

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+

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文件。源程序示例如下：

```csharp
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接口的方法：

```cpp
#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`，以供其他語言編譯器與運行時環境使用，如VB、Delphi、.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的例子：

```idl
coclass SomeClass {
  [default] interface ISomeInterface;
};
```

上述代碼框架聲明了一個COM類，稱為`SomeClass`，實現了接口`ISomeInterface`。

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

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

其中ISomeInterface是一個C++虛基類。

包含COM接口與類的IDL文件被編譯為類型庫（TLB）文件。客戶程序可以在運行時分析類型庫文件，以確定對象支持哪些接口，然後調用對象的接口方法。

C/C++程序以類ID（CLSID）與接口ID（IID）作為參數，用`CoCreateInstance`函數實例化COM對象。`SomeClass`的實例化代碼如下：

```cpp
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支持、VBScript、Visual Basic、ECMAScript（JavaScript）和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（RegFree COM）是Windows XP引入的技術，允許COM組件不在註冊表中存期激活的元數據與類ID（CLSID），而是在實現類的程序集清單或者存儲在可執行文件的資源中或組件安裝時的單獨文件中。這使得同一組件的不同版本可以安裝在不同目錄下，用其各自的manifest描述，直接複製安裝。這種技術有限支持EXE COM伺服器且不能用於系統範圍組件如MDAC、MSXML、DirectX或Internet Explorer。

應用程式裝入時，Windows裝入器搜索manifest。如果存在，裝入器從它增加資訊到激活上下文。COM類工廠試圖實例化一個類時，激活上下文首先檢查這個CLSID的實現是否可以找到。僅當查找失敗時，才掃描Windows註冊表。

### 進程與網路透明

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對象的方法。因此NA可以認為是優化套間之間方法調用的效率。

一個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機制，就需要手工完成安整（通過CoMarshalInterThreadInterfaceInStream函數）與反安整（通過CoGetInterfaceAndReleaseStream函數獲取COM接口的proxy）。例如，把COM接口指針作為線程啟動時的參數傳遞的情形。

跨進程的調用COM對象類似於同一進程內跨套間的調用COM對象。

COM對象coclass在註冊表表示中的子鍵InProcServer32下的條目中ThreadingModel給出：

ThreadingModel的值｜描述

Legacy STA（ThreadingModel=Single或空）｜該COM對象屬於進程的第一個STA線程，通常是UI界面的線程。這是在過去單核CPU時代沒有遺留下來的。

單線程套間（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的線程必須提供線程消息循環處理機制。

多線程套間（MTA），（ThreadingModel=Free）｜COM運行時不提供同步，多個MTA線程可以同時調用同一個MTA的COM對象，由各個MTA線程直接執行COM對象的方法，且因為在同一個MTA中因此不需要安整。COM對象需要自己實現同步控制以避免多線程同時訪問造成的競態條件或死鎖。STA的線程創建MTA的COM對象，系統自動創建一個或多個線程來執行MTA的COM對象。STA線程調用MTA的COM對象也需要marshalling，系統自動分配某個自動創建的線程來執行COM對象。MTA的優點是提高了並發處理性能，同時工作線程不需要有自己的Windows消息循環。

自動選擇套間，（ThreadingModel=Both）｜COM對象的套間類別與創建它的線程的套間類別一致。這避免了很多marshalling開銷，例如一個MTA伺服器被一個STA線程調用。

Thread Neutral Apartment（NA），（ThreadingModel=Neutral）｜一個特殊的套間，沒有任何指定的線程。當STA或MTA線程調用同一進程的NA對象，則調用線程臨時離開它的套間並執行COM對象的代碼，沒有任何線程切換。即任何線程都可以直接了當調用COM對象的方法。因此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組件的約定的精確性，產生了高效的代碼；但不可靠性、開發使用及擴展升級的困難與複雜性高昂。

## 參見

- 可移植對象，跨語言跨平台對象模型定義
- Distributed COM（DCOM），使COM能在網路中工作的擴展
- 通用語言架構，當前的.NET跨語言跨平台對象模型
- Windows Runtime，應用程式模型，指向Windows 8的COM演進版本
- CORBA，通用對象請求代理體系結構，開放式跨平台對象模型
- D-Bus，開放跨語言跨平台對象模型
- KDE Frameworks 5 KDE組件框架
- IBM系統對象模型，功能豐富的COM替代品
- XPCOM，Mozilla應用程式跨平台組件對象模型
- JavaBeans
- Java遠程方法調用
- ICE (中間件)
- 綁紮
- 外界函數接口
- 調用約定
- 名字修飾
- 應用程式接口 - API
- 應用二進位接口 - ABI
- SWIG，開源自動接口綁定生成器，從許多語言到其他語言

## 外部連結

- Microsoft COM Technologies
- Interview with Tony Williams, Co-Inventor of COM （Video Webcast, August 2006）
- A concise technical overview of COM from master's thesis
- Info: Difference Between OLE Controls and ActiveX Controls from Microsoft
- TypeLib Data Format Specification (unofficial) with open source dumper utility.
- The COM / DCOM Glossary