组件对象模型

概要

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克服了这一问题。