COM是Component Object Model的检查,Windows的关键技术之一。本文是Introduction to COM的学习笔记。

COM是一套接口规范,用于在ABI这个底层界面上实现互相调用。所谓接口可以简单理解为一套可以被远程调用的函数指针集合。每个接口通过UUID(defined by the Open Software Foundation (OSF) Distributed Computing Environment (DCE)),而不是名字来标识。接口中的函数则是通过名字来标识。

所谓远程调用,是指COM不仅可以跨越可执行文件的边界,还可以跨越网络。因为COM定义了一层网络协议,可以把调用相关的信息打包成可以在线上传输的格式。

根据Introduction to COM的介绍,COM支持

  • A binary standard for function calls between components.
  • A provision for strongly-typed groupings of functions into interfaces.
  • A base interface that provides polymorphism, feature discovery, and object lifetime tracking.
  • A mechanism that uniquely identifies components and their interfaces.
  • A component loader that creates component instances from a deployment.

COM需要一套框架来支持,框架中的角色包含:

  • A host system that provides a run-time environment that conforms to the COM specification.
  • Interfaces that define feature contracts, and components that implement interfaces.
  • Servers that provide components to the system, and clients that use the features provided by components.
  • A registry that tracks where components are deployed on local and remote hosts.
  • A Service Control Manager that locates components on local and remote hosts and connects servers to clients.
  • A structured storage protocol that defines how to navigate the contents of files on the host’s file system.

既然是规范,就得有描述规范的语言。 COM使用IDL(Interface Defining Language)来描述接口。从IDL可以生成不同语言所需要的定义文件(如C++的头文件)。

要实现COM的接口,所使用的语言必须支持函数指针列表。C++的纯虚类可以生成虚函数表,用于实现COM接口。如果使用C语言,也可以使用所有成员为函数指针的结构体来实现COM。

对于COM接口的一套实现叫做一个COM类,用128-bit Class ID (CLSID) 标识。这个标识可以用来跟踪哪些COM类部署在了系统之中。CLSID也是GUID,可以使用CoCreateGuid()来生成。

一个COM组件可能实现了多个接口。有一个接口专门用来查询COM组件实现了哪些接口,那就是IUnkonwn。IUnkonwn包含一个QueryInterface方法来查询所实现的接口。IUnkonwn还有AddRef和Release两个方法来管理接口的引用计数,以此来管理COM的使用周期。

在Window平台上,COM组件既可以存在一个动态链接库中(.DLL),也可以存在一个可执行文件(.EXE)中。Windows会为平台上存在的COM组件提供注册服务,每个COM组件应当将自己的CLSID在平台上注册,使平台知道自己的存在,这样才方便被其他程序使用。其他程序通过CLSID便可以创建COM组件的实例。CoCreateInstance可以用于创建一个COM组件的实例。 CoGetClassObject可以创建同一个CLSID的多个实例。GetActiveObject可以连接到一个已创建的实例。

COM类由COM服务端管理的(请求COM接口的程序叫做客户端)。COM服务端将CLSID关联到COM类,并提供类工厂来帮助创建这些COM类的实例。COM服务端要实现 IClassFactory接口。此接口有CreateInstance方法,可以用来实例化COM类,不过其最终调用的也是CoCreatInstance。COM服务端也是需要通过DLL或者EXE提供,参考Registering COM Applications

SCM(Service Control Manager)用来协助COM客户端和服务端通信,来实例化相应的COM类:

  • A client requests an interface pointer to a COM object from the COM Library by calling a function such as CoCreateInstance with the CLSID of the COM object.
  • The COM Library queries the SCM to find the server that corresponds with the requested CLSID.
  • The SCM locates the server and requests the creation of the COM object from the class factory that is provided by the server.
  • If successful, the COM Library returns an interface pointer to the client.

当COM客户端和COM类的实例联系上之后,就不需要SCM了,二者可以直接沟通。

SCM可以通过下面三种方式启动COM服务器:

  • In-process: The SCM returns the file path of the DLL that contains the object server implementation. The COM Library loads the DLL and queries it for its class factory interface pointer.
  • Local: The SCM starts the local executable which registers a class factory on startup, and its interface pointer is available to the system and clients.
  • Remote: The local SCM acquires a class factory interface pointer from the SCM that is running on a remote computer.

SCM的存在有其必要性。当一个程序通过COM库来请求一个COM类的时候,COM库得找一个固定联络人,就是SCM。SCM启动或者联通COM服务段之后,会返回IClassFactory接口,通过这个接口COM库,或者客户端程序可以直接来实例化相应得COM类. 具体请看Implementing IClassFactory. ' COM类和COM类之间可以是俄罗斯套娃得关系。一个COM类使用另一个COM类提供得接口来实现自身得某些功能,从而提供另一套接口。接口的使用者通常可以成为外层COM,接口的提供者可以成为内层COM。就这样一层套一层。

COM类和COM类之间还可以是聚合的关系。一个COM类可以把其他几个COM类的接口聚合在一起,并向外提供。使得自己看起来好像实现了这么多接口的感觉。更多查看Reusing Objects

COM还支持持久化,将COM对象的内容持久化到硬盘上。COM库支持两种持久化方式:Storage objects和Stream objects。前者支持IStorage接口;后者支持IStream接口。前者有点类似目录;后者有点类似文件。两者皆可用字符串来表征对象的名字。关于Storage对象的命名规范,可以参考[Storage Object Naming Conventions]。COM对象可以实现这几种接口:IPersistStorage, IPersistStream, IPersistFile.

COM支持使用IDataObject来在不同的对象之间进行同一数据调拨(Uniform Data Transfer)。COM定义了两种数据结构用来支持UDT,一种是FORMATETC ,表征通用的剪贴板格式;另一种是STGMEDIUM ,表征内存中的数据格式。查看The FORMATETC StructureThe STGMEDIUM Structure。如果一个COM客户端需要知晓数据更改,可以实现IAdviseSink接口,传给数据源。查看Data Notification

COM支持远程调用,这涉及到Proxy和Stud的概念,原文是这么说额:

To communicate with a COM object, a client always calls an in-process implementation. If the COM object is in-process, the call is direct. If the COM object is out-of-process or remote, COM provides a proxy implementation that forwards the call to the object by using the Remote Procedure Call (RPC) protocol.

A COM object always receives calls from a client through an in-process implementation. If the caller is in-process, the call is direct. If the caller is out-of-process or remote, COM provides a stub implementation that receives the remote procedure call from the proxy in the client process.

Marshaling is the procedure for packaging the call stack for transmission from proxy to stub. Unmarshaling is the unpackaging that occurs at the receiving end. Return values are marshaled and unmarshaled from the stub to the proxy. This kind of communication is also referred to as sending a call over the wire.

COM把marshaling的协议封装在了CoMarshalInterface接口。查看Inter-Object Communication

COM提供两种场景下的安全机制,一种是Activation阶段,另一种是Call阶段。activation阶段是指通过SCM激活COM的实例。SCM会根据预先注册额信息来确保安全。(查看 Activation Security)。对于Call阶段的话,应用程序可以提供配置信息来让COM执行一些检查。检查可以是针对整个进程的,也可以是针对单个对象或者方法。大体上,COM的安全服务可以分为几个大类:

  • 针对被客户端和服务端同时使用通用函数,可以适配自动安全检查来提供验证服务。设计的函数包括CoInitializeSecurity和CoQueryAuthenticationServices。
  • 在客户端代理之上的接口,允许客户端在接口级别应用安全配置。涉及IClientSecurity接口之上的CoQueryProxyBlanket, CoSetProxyBlanket, 和CoCopyProxy 方法。
  • 服务端函数和嗲用上下文接口,允许服务端检查调用者的信息。涉及IServerSecurity接口的CoGetCallContext, CoImpersonateClient, 和CoRevertToSelf方法。

其他参考

(完)