组件对象模型

title: 概要.
content: 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),不随编译器版本而改变 。
title: 历史.
content: 早在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这个名称。
title: COM元件类型.
content: 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接口,所有实作此接口的,都可以支援跨语言的支援。  微软将实作此接口的元件都称为自动化(Automation)元件。
title: 相关技术.
content: COM曾是Windows平台下主要的软件开发平台,并且影响至其他许多相关软件技术。
title: COM+.
content: COM+是微软Windows 2000中,Microsoft Transaction Server的强化实作版本,除了提供基本的元件交易支援外,还提供了松散藕合式事件(loosely-coupled events)与物件共享池(object pooling)等应用程序服务器的能力,成为Windows 2000开始在微软平台上主要的应用程序服务器平台,目前.NET Framework也提供了System.EnterpriseServices命名空间以支援COM+。
title: Distributed COM.
content: Distributed COM是依据RPC(Remote Procedure Call)的规范发展的可以在网络上通讯的COM元件,它将COM元件的能力扩及到网络上,但因为网络安全以及防火墙因素,DCOM无法广泛的流行。
title: .NET.
content: .NET Framework是新一代的Microsoft Windows应用程序开发平台。使用C#开发COM组件,首先创建类型为Class Library的项目,然后在项目的Property中进入Build页,对“Register for COM interop”选项打勾。打开AssemblyInfo.cs文件,设置[assembly: ComVisible(true)],这样就可以生成.tlb文件。源程序示例如下:
title: 技术细节.
content: 不同的COM组件类型用类ID(CLSID)标示,这是一种全局唯一标识符(GUID)。每个COM组件用一个或多个接口来暴露其功能。这些接口也采用GUID唯一标识,称为接口ID(IID)。  COM接口与几种编程语言有语言绑定,如C语言、C++、Visual Basic、Delphi语言、Python以及Windows平台上的几种脚本语言。它们都是通过接口的方法来访问组件。
title: 接口.
content: 所有COM组件都实现了IUnknown接口,该接口暴露了引用计数实现的对象生命期管理与类型转换,以访问不同的预定义接口。  IUnknown接口以及基于IUnknown的定制接口包括一个指向虚函数表的指针,虚函数表中包含若干函数指针,分别指向接口所声明的函数实现。对于进程内的COM组件调用,其效率等同于C++的虚函数调用。  除了基于IUnknown的定制接口,COM也支持继承自IDispatch的dispatch接口,从而支持了用于OLE自动化的晚绑定。不能访问定制接口的编程语言(例如VBS)可以通过dispatch接口访问COM组件。  Windows API提供了C语言定义COM接口的方法:
title: 类.
content: COM类(coclass)是一个或多个接口的具体实现,它很类似面向对象程序设计语言中的类。类的GUID标识被称作类ID(CLSID);或者programmatic identifier字符串(progid),因为VBS等脚本语言不能使用GUID,只能用字符串查找、使用COM组件。  COM对象不能被直接访问,只能通过COM接口来访问对象。COM也支持同一个接口的多个实现,因此客户程序运行时可以选择实例化接口的哪个实现。
title: 接口定义语言与类型库.
content: 类型库(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)文件codice_9,以供其他语言编译器与运行时环境使用,如VB、Delphi、.NET等生成语言相关表示COM类型的结构。C++把TLB转回到IDL表示。
title: #import 类型信息.
content: 使用C++的预编译directivecodice_10,可以装入如下格式的类型信息: codice_10创建两个头文件以用C++源码形式恢复类型库信息: 两个文件被放在输出目录中。编译器在现场就地codice_12主头文件。  类型库主头文件(.TLH)包含七部分: 从第2至第6部分都包含在命名空间中,其名字在最初的IDL文件的codice_16语句中给出。改名字在codice_10语句中可用属性no_namespace抑制掉;也可用rename_namespace属性更名。
title: COM作为对象框架.
content: COM是一个运行时框架,类型必须在运行时单独地标识并可指定。为此,使用GUID,每个COM类型被指定了它自己的GUID用于运行时标识。这也解决了C/C++语言的名字修饰导致的链接兼容性问题。  为了使COM类型信息在编译时与运行时都可以访问,COM使用类型库。这使得COM成为对象交互的动态框架。  考虑下述用IDL定义coclass的例子:  上述代码框架声明了一个COM类,称为codice_18,实现了接口codice_19。  这在概念是等价于下述C++类:  其中ISomeInterface是一个C++虚基类。  包含COM接口与类的IDL文件被编译为类型库(TLB)文件。客户程序可以在运行时分析类型库文件,以确定对象支持哪些接口,然后调用对象的接口方法。  C/C++程序以类ID(CLSID)与接口ID(IID)作为参数,用codice_20函数实例化COM对象。codice_18的实例化代码如下:  在这个例子中,使用COM子系统获取指向codice_19接口的实现对象的指针,用CLSID_SomeClass指示用这个特定的coclass。
title: 引用计数.
content: 所有COM对象采用引用计数管理对象的生命期。客户程序通过所有COM对象都要强制实现的IUnknown接口的AddRef与Release方法来控制引用计数。当引用计数降到0时,COM对象自己负责释放内存。即对动态分配内存创建的COM对象,其Release函数内部引用计数降为0时,就释放自身所占的动态分配内存。有的COM对象(如IClassFactory)往往是静态对象,Release函数内部引用计数降为0时不需做额外的操作。  特定语言(例如Visual Basic)提供了自动引用计数,所以COM对象开发者在源代码中不需要显式维护任何内部的引用计数。C/C++编程者或者执行显式的引用计数,或者使用智能指针(如MFC提供的CComPtr)自动管理引用计数。  下述是如何调用COM对象的AddRef与Release的指引:  不向远程对象发出引用计数的调用。代理模块保持着远程对象的一个引用,并维持着它自己的本地引用计数。  为简化COM开发,引入了活动模板库(Active Template Library,ATL)用于C++开发。ATL提供了更高层次的COM开发范式。ATL也有益于COM客户应用程序开发摆脱直接维护引用计数,而是用智能指针对象。  其他能直接支持COM的库与语言还包括MFC Visual C++编译器的COM支持、VBScript、Visual Basic、ECMAScript(JavaScript)和Borland Delphi等。
title: 程序设计.
content: COM是一个语言独立的二进制标准,任何能够理解与实现COM的二进制定义的数据类型与接口的语言都可以开发COM组件。  COM实现负责进入、离开COM环境,实例化与引用计数COM对象,查询对象支持的接口,以及错误处理。  Microsoft Visual C++编译器支持对C++语言的扩展:称作"C++ Attributes"。这些扩展被设计用于简化COM开发,去除实现COM服务器时大量臃肿的代码。
title: 使用注册表.
content: 在Windows操作系统中,COM类、接口、类型库都会根据其GUID登记到Windows注册表。HKEY_CLASSES_ROOT\CLSID下是COM类;HKEY_CLASSES_ROOT\Interface下是接口。COM类型库注册在每个COM对象的本地库条目下或者远程服务的网络位置处。
title: 不使用注册表的COM.
content: 不使用注册表的COM(RegFree COM)是Windows XP引入的技术,允许COM组件不在注册表中存期激活的元数据与类ID(CLSID),而是在实现类的assembly manifest或者存储在可执行文件的资源中或组件安装时的单独文件中。这使得同一组件的不同版本可以安装在不同目录下,用其各自的manifest描述,直接复制安装。这种技术有限支持EXE COM服务器且不能用于系统范围组件如MDAC、MSXML、DirectX或Internet Explorer。  应用程序装入时,Windows装入器搜索manifest。如果存在,装入器从它增加信息到激活上下文。COM类工厂试图实例化一个类时,激活上下文首先检查这个CLSID的实现是否可以找到。仅当查找失败时,才扫描Windows注册表。
title: 进程与网络透明.
content: COM对象可以透明地实例化与引用在同一进程、跨进程边界、甚至在网上远程(DCOM)。进程外或远程对象用marshalling序列化方法调用与返回值。这种marshalling对用户是不可见的,就如同访问进程内的COM对象。
title: 线程化与“套间”.
content: 一个进程加载了一个COM的DLL文件后,该DLL可能定义并使用了一些可修改的全局变量或访问共享资源。该进程内的多个线程如何并发访问该DLL并保证是线程安全的,这就是“套间”(apartment)技术需要解决的问题。  COM对象与创建或调用COM对象的线程可以按两种策略来实现并发安全:  COM的并发安全的具体实现,提出了套间(apartment)概念。每一种套间类型表示在一个进程内部是多线程情况下,如何同步对COM对象的调用。套间是一个逻辑容器,收纳遵循相同线程访问规则的COM对象与COM线程(创建了COM对象的线程或者调用了COM对象的方法的线程)。套间本质上只是一个逻辑概念而非物理实体,没有句柄类型可以引用它,更没有可调用的API操纵它。套间有两种:  一个COM对象只能存在于一个套间。COM对象一经创建就确定所属套间,并且直到销毁它一直存在于这个套间。COM对象有4种套间模式:单线程套间(Single Threading Apartment,STA),多线程套间(MTA),线程中立套间(Thread Neutral Apartment,NA),以及Both(STA+MTA)。详见下表。COM对象的套间类型写在Windows注册表相关条目中。  一个COM线程从创建到结束都属于同一个套间。COM线程只有两种套间模式:STA或MTA。线程必须通过调用CoInitializeEx()函数并且设定参数为COINIT_APARTMENTTHREADED或者COINIT_MULTITHREADED,来指明该线程的套间模式。调用了CoInitializeEx()函数的线程即已进入套间,直到线程调用CoUninitialize()函数或者自身终止,才会离开套间。COM为每个STA的线程自动创建了一个隐藏窗口,其Windows class是"OleMainThreadWndClass" 。跨套间调用这个STA套间内的COM对象,实际上是向这个隐藏窗口发送了一条窗口消息,通过消息循环与分派,该窗口过程收到这条窗口消息并调用相应的COM对象的接口方法。  线程与属于同一套间的对象遵循相同的线程访问规则,可以直接执行方法调用而不需COM的辅助。线程跨套间边界去调用COM对象,传递的指针需要marshalling。如果通过标准的COM的API来调用,可以自动完成安整。例如,把一个COM接口指针作为参数传递给另外一个套间的COM对象的proxy的情形。但如果软件编程者跨套间传递接口指针而没有使用标准COM机制,就需要手工完成安整(通过CoMarshalInterThreadInterfaceInStream函数)与反安整(通过CoGetInterfaceAndReleaseStream函数获取COM接口的proxy)。例如,把COM接口指针作为线程启动时的参数传递的情形。  跨进程的调用COM对象类似于同一进程内跨套间的调用COM对象。
title: 批评.
title: 消息泵.
content: STA初始化时,创建一个隐藏窗口,用于apartment之间、进程间的消息路由。该窗口必须有正常的消息队列泵。这种结构称为消息泵。早期版本的Windows,消息泵的失败会导致系统范围的死锁。这个问题被初始化COM的Windows API复杂化了,并会导致实现细节的泄露。
title: 引用计数.
content: 如果多个对象是循环引用,则可能会导致问题。  Objects may also be left with active reference counts if the 使用COM事件池(event sink)模型,则对象可能一直保持活动的引用计数而不能被销毁。因为发送事件的对象必须有处理事件的对象的引用,因而对象引用计数永远不为0.  引用循环可以采取下述技术来克服:
title: DLL地狱.
content: 进程内的COM组件是用DLL文件实现,每个版本的DLL用CLSID登记到Windows注册表,因而某些情况下会发生DLL Hell效应。无需注册的COM克服了这一问题。
title: 组件间的约定的表示.
content: COM组件间的约定,纯粹是通过用户与组件之间的语义保证和假设的形式来表示的。COM用类型的形式表示组件约定。但是该约定存在如下两个关键问题,使得其对语义的表示并不是最优的。

Summary: 组件对象模型(英语:Component Object Model,缩写英语:COM)是微软的一套软件组件的二进制接口标准。这使得跨编程语言的进程间通信、动态对象创建成为可能。COM是多项微软技术与框架的基础,包括OLE、OLE自动化、ActiveX、COM+、DCOM、Windows shell、DirectX、Windows Runtime。