COM - COM的简单介绍

在此文章记录一下 COM 的简单介绍,包括 COM 相关技术要点以及编程的过程,方便以后查阅。

目录

1、概述

2、COM结构

2.1、COM对象

2.2、COM接口

2.3、IUnknown接口

2.3.1、引用计数

2.3.2、接口查询 QueryInterfqace

2.4、接口描述语言IDL

2.5、COM对象和接口图示法

3、COM实现

3.1、进程内组件和进程外组件

3.2、通过注册表管理COM对象

3.3、类厂

3.3.1、类厂的定义

3.3.2、类厂的使用

3.4、COM库

3.5、COM实现过程

3.5.1、进程内组件与客户的协作过程


1、概述

COM,Component Object Model,即组件对象模型,是一种以组件为发布单元的对象模型,这种模型使各软件组件可以用一种统一的方式进行交互。COM 既提供了组件之间进行交互的规范,也提供了实现交互的环境,因为组件对象之间交互的规范不依赖于任何特定的语言,所以,COM也可以是不同语言协作开发的一种标准。

COM实际上是一种组件标准,COM不仅仅提供了组件之间的接口标准,它还引入了面向对象的思维。在COM标准中,对象是一个非常活跃的元素,常常被称为COM对象。组件模块为COM对象提供了活动的空间,COM对象以接口的方式提供服务,这种接口就被称为COM接口。COM组件、COM对象、COM接口三者的关系如下图所示:

在Windows系统平台上,一个COM组件可以是一个DLL(Dynamic Linking Library,动态链接库)文件,也可以是一个EXE(可执行程序)文件。一个组件程序可以包含多个COM对象,并且每个COM对象可以实现多个接口。

当另外的组件或者普通程序(即组件的客户程序)调用组件的功能时,它首先创建一个COM对象或者通过其他途径获得COM对象,然后通过该对象所实现的COM接口调用它所提供的服务。当所有的服务结束之后,如果客户程序不再需要该COM对象,那么它就应该释放掉对象所占用的资源,包括对象自身。

2、COM结构

2.1、COM对象

COM是面向对象的软件模型,因而对象是它的基本要素之一,那么COM对象是什么呢?类似于C++中对象的概念,对象是某个类(Class)的一个实例;而类则是一组相关的数据和功能组合在一起的一个定义。使用对象的应用(或另一个对象)被称为客户,有时也可称为对象的用户

在COM中,每个对象用一个128位的GUID来标识,被称为CLSID(Class Indentifier, 类标识符或类ID)。定义的格式如下所示:

这个GUID的值可以通过编译器的工具进行自动生成。

2.2、COM接口

接口是一组逻辑上相关的函数集合,其函数也被称为接口成员函数。按照习惯,接口名常以"I"为前缀,比如下一章的 IUnknown 接口。对象通过接口成员函数为客户提供各种形式的服务

在COM模型中,对象本身对于客户是不可见的,客户请求服务时,只能通过接口进行。那客户是怎么获取指定的接口呢?与对象类似,接口采用的仍然是128位的 GUID(Globally Unique Identifier, 全局唯一标识符),只不过名称不同。客户通过GUID获取接口指针,再通过接口指针,客户就可以调用其相应的成员函数了。至于具体功能如何实现,则完全有对象的接口内部实现。所以,在COM模型中,对象通过接口及接口中的函数为客户提供服务,对于客户来说,它只与接口打交道。一般来说,接口是不变的,只要客户期望的接口在组件对象中还存在,它就可以继续使用该接口提供的服务。对象可以支持多个接口,因此对组件对象的升级可以通过增加接口的办法来实现,这样得到的新接口可以不影响老接口的使用。新客户可使用新增的接口,老用户可以在不更新代码的情况下继续使用老接口。

COM接口和经常说的API有点相似。通过API接口层,可以很好地把两个程序连接起来,但存在一些问题:1)、当API非常多时,使用会非常不方便,需要对函数进行组织。2)、API函数需要标准化,按照统一的调用方式进行处理,以适应不同编程语言的实现,包括参数传递顺序、参数类型、函数返回处理都需要标准化。而COM定义了一套完整的接口规范,不仅可以弥补以上API作为组件接口的补足,还充分发挥了组件对象的优势,并实现了组件对象的多态性。

从技术上讲,接口是包含了一组函数的数据结构,通过这组数据结构,客户代码可以调用组件对象的功能。客户程序用一个指向接口的数据结构指针来调用接口成员函数。调用过程和C++中的虚函数的调用很相似。接口的结构如下所示:

如果是在C++中,接口的定义就是一个包含了一组纯虚函数的抽象类。接下来会使用C++作为开发语言完成COM的编程实例。

以字典组件为例,字典组件的继承树如下所示:

IDictionary的字典接口,提供了插入单词、删除单词等功能,则接口的定义以及内存机构如下所示:

那么对象和接口之间到底有什么关系呢?为了方便理解,以C++举例,对象实际上是实现了接口类的实现类的一个实例。比如,有一个类CDictonary,它实现了接口IDictionary,则对象就是类CDictonary的一个实例。

,则接口 IDictionary和CDictonary对象的内存结构如下所示:

 是不是和C++中抽象类的用法是一样的。

2.3、IUnknown接口

COM规定,每一个接口都必须从IUnknown接口继承过来,那是因为在 IUnknown接口提供了两个非常重要的特性:生存周期控制和接口查询,IUnknown引入了“引用计数”(reference counting)的方法,可以有效的控制对象的生存周期。IUnknown使用了“接口查询”(QueryInterfqace)的方法提供接口查询的功能,可以完成接口之间的跳转。

IUnknown接口的C++定义如下:

IUnknown包含了三个成员函数。函数 QueryInterfqace用于查询COM对象的其他接口指针,函数 AddRef和Release用于对引用计数进行操作。所以,在COM中上述的 IDictionary接口需要定义成如下形式:

 IDictionary除了定义了自己的接口函数,还从IUnknown中继承了上述的三个接口函数。

2.3.1、引用计数

通常比较合理的方式是采用对象一级的引用计数以便控制对象的组件的生存周期。如果是在对象一级实现引用计数,可以使用C++类的成员变量来实现。以前面的类CDictonary和接口IDictionary为例,在CDictonary类定义了一个私有成员变量 m_Ref用于引用计数。实现引用计数的方式如下所示:

    

2.3.2、接口查询 QueryInterfqace

对于接口查询函数 QueryInterfqace,其中输入参数 iid 为接口标识符,输出参数 ppv 为查询得到的结果的接口指针,如果对象没有实现 iid 所标识的接口,则输出参数 ppv 指向空(NULL)。返回值 HRESULT 是一个32位的整数,具有以下三种结果:

  1. S_OK:查到了指定的接口,接口指针存放在 ppv 输出参数中;
  2. E_NOINTERFACE:对象不支持所指定的接口,*ppv为NULL;
  3. E_UNEXPECTED:发生了以外错误,*ppv为NULL;

使用方式参考如下:

实现方式参考如下:

上图中,当iid == IID_IUnknown时,并没有转成为IUnknown类型,是因为,根据上文中继承树的概念,如果返回的是 IUnknown 类型的指针是存在二义性的,因为 CDictionary继承了两个接口,而这两个接口又都继承自IUnknown,所以为了避免出现二义性,则直接转换为IDictionary类型。

2.4、接口描述语言IDL

IDL,Interface Description Language,接口描述语言。IDL提供了一种不依赖于任何语言的接口描述方法,因此,它可以成语组件程序和客户程序之间的共同语言。

COM规范使用的IDL接口描述语言不仅可用于定义COM接口,同时还定义了一些常用的数据类型,也可以描述自定义的数据结构,对于接口成员函数,我们可以指定每个参数的类型、输入输出特性、甚至支持可变长度的数组的描述。IDL支持指针类型,与C/C++很类似。例如上面的 IDictionary接口用IDL描述如下所示:

,可以通过MIDL工具,将IDL接口描述文件编译成C/C++兼容的接口描述头文件(.h)。

2.5、COM对象和接口图示法

COM对象和接口有一种特殊的图形描述方法。CDictonary的对象和接口图形化描述如下图所示:

3、COM实现

3.1、进程内组件和进程外组件

进程内组件:如果用动态链接库 DLL的方式实现组件程序,则客户程序调用组件程序的服务时,会把组件程序装入到自己的进程中,所以客户程序和组件程序是运行在同一个进程中,就把这种组件程序称为进程内组件。

进程外组件:实现组件程序的另一种方式是EXE程序,这种组件程序在被调用时有其自己的进程空间,所以客户程序和组件程序运行在不同的进程空间中,这种组件程序就被称为进程外组件。

本文只考虑进程内组件。

3.2、通过注册表管理COM对象

在前面第2章中已经提到,COM规范使用128位的GUID来标识COM对象和接口,客户程序通过这些GUID来创建COM对象并与对象进行交互。按照COM规范,客户程序通过COM库完成对象的创建工作(将在3.4章节讲述)。COM库通过系统注册表(system registry)所提供的信息进行组件的创建工作。

组件程序把它所实现的COM对象信息以及接口信息都保存到注册表中,这个过程被称为组件注册,如果组件程序具有这种自注册能力则称该组件程序为自注册组件。客户程序在创建组件对象时,也需要直接或间接地访问注册表中的信息。通常,组件对象的创建工作由COM库来完成,COM库所进行的很多操作都要依赖系统注册表提供的信息才能完成。

进程内组件的自注册过程:对于进程内组件,因为它只是一个动态链接库,本身不能直接运行,所以必须被某个进程调用才能获得控制。Windows提供了一个用于进程内组件的实用工具RegSvr32.exe,只要进程内组件提供了相应的入口函数,则RegSvr32就可以完成注册或注销工作。组件程序的两个用于注册的入口函数为 DllRegisterServer DllUnregisterServer

RegSvr32程序本身并不进行注册工作。所以当用下面的命令行方式运行 RegSvr32时【RegSvr32 c:\**.dll】,RegSvr32调用组件程序 **.dll 中的 DllRegisterServer 函数完成组件程序的注册工作;当用下面的命令行方式运行 RegSvr32时【RegSvr32 /u c:\**.dll】,RegSvr32调用组件程序 **.dll 中的的 DllUnregisterServer 函数完成组件程序的注销工作所以,进程内组件实现自注册的工作就变成了提供两个引出函数 DllRegisterServer 函数和 DllUnregisterServer 函数,在函数体内完成组件对象的信息的注册或注销操作

3.3、类厂

创建组件对象时,客户程序调用COM库中的函数进行组件对象的创建工作,COM库的创建函数根据注册表的信息并调用组件程序的入口函数来创建组件对象。所以组件程序需要提供一个标准的入口函数 DllGetObjectClass 函数,用于提供本组件的组件信息。而在 DllGetObjectClass 中,是以类厂的方式获取组件对象的。

3.3.1、类厂的定义

类厂,顾名思义,就是COM类的工厂。如果对C++比较熟悉的话,应该会知道设计模式中的工厂设计模式,其实这个类厂的概念就和工厂设计模式很相似。确切的说,类厂应该成为“对象厂”,因为类厂是COM对象的生产基地,COM库通过类厂创建COM对象;COM规定,每一个COM类,对应的都要有一个类厂专门用于该COM类的对象的创建工作

类厂本身也是COM对象,它支持一个特殊的接口 IClassFactory,前面已经讲过,在COM中,所有的接口都要继承IUnknown接口,所以 IClassFactory接口定义如下:

通过接口成员函数 CreateInstance 创建对应的COM对象。因为每个类厂只只对特定的COM类对象,所以 CreateInstance 函数知道该创建什么样的COM对象。CreateInstance 函数的参数含义如下:

  1. pUnknownOuter:用于对象类被聚合的情形,一般设置为NULL
  2. iid:为对象创建完成后客户应该得到的初始接口IID
  3. ppv:存放返回的接口指针

IClassFactory 的另一个成员函数 LockServer用于控制组件的生存周期,在此不多做描述。

如果一个组件程序实现了多个COM对象类,则相应的有多个类厂。所以,上述关于字典组件的结构、和多个类厂的结构就如下所示:

 

对于多个类厂的情况,可以设计一个比较通用的类厂代码,把不同的对象类的信息放到一个数据结构中,当客户程序请求创建某个CLSID的类厂时,可以选择对应的信息结构,然后返回类厂结构指针。

3.3.2、类厂的使用

因为类厂本身也是个COM对象,它被用于其它COM对象的创建过程,那么类厂对象又由谁来创建呢?就是前文中提到的 DllGetObjectClass 引出函数!DllGetObjectClass 函数原型如下所示:

 参数含义如下:

  1. clsid:待创建对象的CLSID,因为一个组件可能实现了多个COM对象类,所以必须指定
  2. iid:接口的IID(GUID)
  3. ppv:存放类厂接口指针

COM库在接到对象创建指令后,它要调用进程内组件的 DllGetObjectClass 函数创建类厂对象,并返回类厂对象的接口指针,COM库或者客户一旦有了类厂的接口指针,他们就可以通过类厂接口 IClassFactory 的成员函数 CreateInstance  创建相应的COM对象。

3.4、COM库

COM除了定义了组件程序和客户程序交互的规范以外,它也提供了COM的实现部分即COM库,使得这些规范能够真正地应用起来。并且COM库也充当了组件程序和客户程序之间的桥梁,尤其是在组件对象的创建过程中,以及在对象管理、内存管理和一些标准化操作方面起着重要的作用。

COM库的一些常用函数:

客户程序调用COM库创建组件对象的顺序图:

3.5、COM实现过程

整体介绍一下COM客户程序、COM库、COM组件程序三者之间的协作过程,算是对前面讲述内容的一个概括描述。

3.5.1、进程内组件与客户的协作过程

下面就以前面介绍的字典组件为例,简单概括一下协作的过程。

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值