2023年11月30日发(作者:)

使用ATL开发COM组件

ATL是用来开发COM组件的类库,是VC中继MFC之后的第二大类库,内容也非常

多,短时间之内不可能学完。我们这里只是抛砖引玉。实用率:30%不到。

一、COM原理

1、什么是COM

COM是组件对象模型的简写(Component Object Model),它是一套二进制对象模型,可

跨平台、跨语言、跨网络(分布式的),也称为COM组件。它可以用任何语言编写,都能

完成相同的功能,并能够复用。

软件的复用有五类:源代码复用,模块复用,接口复用,程序复用,程序架构复用。

COM组件技术是模块复用的最高形式。用组件开发软件象搭积木一样,可以拼装成一个大

组件或大系统。

COM并不是一种语言,而是一种组件开发的标准。它描述了对象模型建立,对象间的

通信等,因此COM更象一个二进制组件重用的协议。

COM有关的程序员有两种:组件开发者,组件使用者

2COM组成

COM组件是由以Win32动态链接库DLL或可执行文件EXE发布的代码所组成。

3、为什么要学习COM

传统的软件开发模式:传统的软件应用程序发布以后,使用者想得到更完善的软件或者

去掉其中的某些内容,只有等软件提供商发布新的版本以后,使用者的这一梦想才能得以实

现。

使用COM组件以后:软件开发人员可以在软件版本发布以后修改或者去掉软件某个不

需要部分。对应用程序在更高的层次上定制,使得软件更具灵活性、动态性。是未来应用软

件开发的趋势(10年前可以这么说,现在未必)

4COM的前身是OLEObjectLinkingandEmbedding,即对象链接与嵌入。

5、组件:如积木一样,具有“块”的概念,可以动态的将他们插入或卸出应用程序。这就

需要两个条件:第一,组件必须动态链接(不因自己的位置改变而改变自身形式);第二,

组件必须隐藏内部实现细节(独立于具体编程语言,二进制形式发布)。每个组件相当于一

个黑盒子,对外提供的只是接口(函数)。如果接口没有发生任何变化时,对组件的修改几

乎不会影响应用程序的其它部分。提供服务的组件称为服务器组件使用服务的组件称为

户组件

6COM的设计思想

COM的设计思想是为了重用而且是二进制级别的代码重用。类库也是重用,MFC

但只是基于源码级别的重用,类库严重依赖于固定的某种语言,如MFC依赖于VCPFC

依赖于PowerBuilder等。DLL也是重用,但也有很大的局限:函数重名问题、各编译器对

函数的名称修饰不兼容问题、路径问题、DLLEXE的依赖问题。

COM没有重名问题,因为根本不是通过函数名来调用函数,而是通过虚函数表,自然

也不会有函数名修饰的问题。路径问题也不复存在,因为是通过查注册表来找组件的,放在

什么地方都可以,即使在别的机器上也可以。也不用考虑和EXE的依赖关系了,它们二者

之间是松散的结合在一起,可以轻松的换上组件的一个新版本,而应用程序混然不觉。

7、学习COM的目的

要学COM的基本原理,推荐看《COM技术内幕》。但仅看这样的书是远远不够的。其

实,我们最终的目的是要学会怎么应用COM去编写我们的应用程序,而不是拼命的研究

COM本身的机制。所以我个人觉得COM的基本原理不需要花大量的时间去追根问底,

没有必要,是吃力不讨好的事。其实我们只需要掌握几个关键概念就够了。

8、用VC开发COM组件所要了解的几个关键概念

(1)COM组件实际上是一个C++类,而接口都是纯虚类。

(2)COM组件有三个最基本的接口类,分别是IUnknownIClassFactoryIDispatch

其中,IUnknown包含三个函数,分别是QueryInterfaceAddRefRelease

(3) Dispinterface接口、Dual接口以及Custom接口

(4)COM组件有三种:进程内COM、本地COM、远程COM。即它可以是进程内的,

即和调用者在同一个进程内;也可以和调用者在同一个机器上但在不同的进程内;还可以根

本就和调用者在两台机器上。

(5)COM组件的核心是IDL

IDL是一种用来定义COM接口规范的、大家都认识的接口定义语言,用它来定义的接

口,不论放到哪个语言平台上都认识它。用VCVB等语言都可以开发COM组件,开发

好的COM组件也可以被其他语言调用。

(6)、注册表在COM中的重要作用

首先要知道GUID的概念,COM中所有的类、接口、类型库都用GUID来唯一标识,

GUID是一个128位的字串,根据特制算法生成的GUID可以保证是全世界唯一的。

COM组件的创建接口、查询接口都是通过注册表进行的。有了注册表,应用程序就不

需要知道组件的DLL文件名、位置,只需要根据CLSID查就可以了。当版本升级的时侯,

只要改一下注册表信息,就可以神不知鬼不觉的转到新版本的DLL

(7)、一个典型的自注册COM DLL所必有的四个函数

DllGetClassObject:用于获得类厂指针

DllRegisterServer:注册一些必要的信息到注册表中

DllUnregisterServer:卸载注册信息

DllCanUnloadNow:系统空闲时会调用这个函数,以确定是否可以卸载DLL

(8)COM组件的运行机制

通过查注册表CLSID_Object,得知组件DLL的位置、文件名

装入DLL

使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针

调用DllGetClassObject

9、完整的COM组件的接口实现示例:代码见“erface1”工程,这里无需掌握,

只是帮助理解COM的原理。

10、增加查询组件接口QueryInterface()、应用计数增加接口AddRef()、应用计数减少接口

Release()后的完整例子。代码见“erface2”工程,这里无需掌握,只是帮助理解

COM的原理。

二、ATL活动模板库(Active Template Library

ATL产生以前,开发COM组件的方法主要有两种:一是使用COM SDK直接开发

COM组件,另一种方式是通过MFC提供的COM支持来实现。但是这两种方法都非常的复

杂、麻烦。

使用ATL开发COM应用是一件比较简单的事情,但是在ATL简单易用的界面后面却

包含着复杂的技术。这些技术包含以下一些方面:

COM技术

C++模板类技术(Template)

C++多继承技术(Multi-Inheritance)

其中,COM技术本文开头有所描述。C++模板类技术(Template)C++多继承技术

(Multi-Inheritance)C++理论课中已有详尽描述。这里最主要的是介绍使用ATL怎么生成

一个自己的COM组件,以及怎么使用该组件。

预备知识:ATL中的数据类型BSTR

BSTRBASIC 中字符串类型的表示方式,是一个指向 UNICODE 字符串的指针。

BSTR 的处理函数,参见下表。

API 函数说明

SysAllocString()

SysFreeString()

SysAllocStringLen()

SysAllocStringByteLen()

SysReAllocStringLen()

释放 BSTR 内存

申请一个指定字符长度的 BSTR 指针,并初始

化为一个字符串

申请一个指定字节长度的 BSTR 指针,并初始

化为一个字符串

重新申请 BSTR 指针

申请一个 BSTR 指针,并初始化为一个字符串

CString 函数 说明

AllocSysString()

SetSysString()

CString 得到 BSTR

重新申请 BSTR 指针,并复制到 CString

CComBSTR 函数

ATL BSTR 包装类。在 atlbase.h 中定义

Append ()AppendBSTR()AppendBytes()

太多了,但从函数名称不能看出其基本功

ArrayToBSTR()BSTRToArray()

能。详细资料,查看MSDN 吧。另外,左侧

AssignBSTR() Attach()Detach()Copy()

函数,有很多是 ATL 7.0 提供的,VC6.0 下所

CopyTo()Empty()Length()ByteLength()

带的 ATL 3.0 不支持。

ReadFromStream()WriteToStream()

由于我们将来主要用 ATL 开发组件程序,

LoadString()ToLower()ToUpper()

因此使用 ATL CComBSTR 为主。

运算符重载:!,!=,==,<,>,&,+=,+,=,BSTR

ATL提供了CComBSTR类来简化使用BSTR(见上表)

ATL中的数据类型VARIANT

C++BASICJavaPascal计算机语言多种多样,而它们各自又都有自己的

数据类型,COM 产生目的,其中之一就是要跨语言。而 VARIANT 数据类型就具有跨语

言的特性,同时它可以表示(存储)任意类型的数据。

ATL提供CComVariant类来简化使用VARIANT,如:VARIANT v = CComVariant(任意

C++类型);

下面我们使用ATL编写一个“将一个字符串转换成大写然后倒置”COM组件:

1.建立一个ATL COM AppWizard工程AtlComSample

2.选择创建的COM类型:

该步骤中的一些选项说明:

动态连接库(DLL:最终产生一个动态连接库形式的COM服务程序(DLL)

应用程序(Executable:最终产生一个自注册的可执行的COM服务程序(EXE)

NT服务(Service:产生一个以NT服务方式运行的COM服务程序(EXE)

允许嵌入Proxy/Stub代码。由Microsoft提供的MIDL编译IDL文件以后,将产生用于

对象调度(Marshaling)的Proxy/Stub的代码。在传统方式下,这部分代码与COM服务程

序的代码是分离的,但是由于新的COM标准支持多线程环境下的COM对象服务,因此在

动态连接库的COM服务程序中也要有Proxy/Stub的支持。为了支持在网络上的传输,ATL

允许用户选择将Proxy/Stub的代码包括在生成的DLL代码中。这个选项在EXENT服务

类型的COM应用条件下不可选。

允许支持MFC由于ATL对除COM以外的基本的Windows编程方面的支持极为有限,

同时许多程序员对MFC又非常熟悉,因此在ATL的工程设置中允许在ATL工程内部支持

使用MFC即可以使用MFC定义的类。这一特点给开发人员提供了许多方便,特别对于习

惯使用MFC的人来说,能够使用MFC提供的各种功能强大的类的支持,而不必直接使用

WindowsSDK。从另一个方面来看,在ATL工程中使用MFC同时就丧失了ATL代码轻量

级的特点。

支持MTSMTSMicrosoftTransactionServer的缩写,它是MicrosoftCOM技术方

面的一个新的分支,这里不做详细说明。----完成上面的设置以后,可以选择Finish完成工

程的设置,ATL将创建相应的工程。

我们选择默认选项,不做任何改动。

3.向工程中加入一个新的ATL类:

首先通过集成环境的"Insert"菜单下的""命令进入"ATLObjectWizard"

话框。如下图所示:

对话框的左边部分说明了待创建对象的基本类型,这里主要有以下几种类型:

对象(Object:基本的COM对象类型;

控件(ControlActiveXControl类型的ATL对象;

其他(Miscellaneous:辅助功能,如对话框的生成等;

数据访问(DataAccess:数据访问,支持MTS等。

对于我们这个一般的COM服务程序,左侧选择对象Object右侧选择Simple Object

点击下一步继续:

4.输入我们这个组件的类名CMyString,其他名字会自动产生,不做改动,点确定:

5.切换到Attributes(属性)标签页:

对象的属性设置是ATL对象创建过程中最复杂的部分,包括以下几个主要部分:

对象的线程模型(ThreadModel:对象的线程模型是COM对象在多线程环境下被访问时对

访问方式的控制,缺省情况下在ATL中采用的是套间模型Apartment由系统通过消息队列

方式提供并发控制。

对象的接口模型(InterfaceCOM对象的接口可以是双接口(DualInterface)。双接口不同于

普通接口(CustomInterface)之处在于双接口是从Automation基本接口IDispatch继承的,而普

通接口是从IUnknown接口直接继承来的。缺省的接口模型是双接口。

对象的聚合模型(AggregateCOM规范不允许对象的实现继承,但是可以通过聚合方式

重用其他的COM对象。ATL对象属性设置中的聚合模型可以指定待创建的COM对象是否

支持聚合模型。缺省的选项是支持对象的聚合。

对象对错误处理的支持(SupportISupportErrorInfo):选取这个选项可以在对象的运行过程中

支持错误处理。缺省情况下这个选项不被选中。

对象对连接点的支持(SupportConnectionPoints):连接点是COM对象的事件机制。选中这个

选项可以使待创建的COM对象具有发出事件的能力。缺省情况下该选项不被选中。

对象对自由线程调度的支持(FreeThreadMarshaller,简称FTM):对象的自由线程调度是对象

在处于自由线程模型状态下,为了简化对象的访问过程而采用的一种优化策略。缺省情况下

该选项不被选中。

这里我们也使用默认选项,确定。此时在“ClassView”标签页会增加一个CCMyString类和

一个ICMyString接口。

6.为ICMyString接口添加一个方法StringTransfer(),其原型我们定义如下:

HRESULT StringTransfer([in] BSTR InData, [out] BSTR *OutData, [out, retval] long *result);

其中,[in]表示参数方向是输入;[out]表示参数方向是输出;[out,retval]表示参数方向是输出,

同时可以作为函数运算结果的返回值。一个函数中,可以有多个[in][out],但[retval]只能

有一个,并且要和[out]组合后在最后一个位置。

中,给添加的方法加入如下代码

CComBSTR strSource = InData;

CComBSTR tempSource = InData;

bool bNeedToUpper = true;

int j = (int)() - 1;

//实现字符串到大写字母的转换并进行逆序转换

for (int i = 0; i < (int)(); i++)

{

bNeedToUpper = true;

}

if (strSource[i] == ' ') bNeedToUpper=false;

tempSource[j] = strSource[i];

j = j - 1;

}

*OutData = tempSource;

return *result;

编译链接,通过。COM组件开发完毕。

Debug

,在工程目录下生成了一个类型库文件。这2个文件就

是用ATLCOM组件的成果。你现在就可以发布这2个文件给另外的COM使用者了。

中的DLL文件就是COM组件,使用之前必须使用进行注册。TLB文件是提

供给开发者使用的在后面使用COM组件的例子中我们会看到是如何使用该TLB文件的。

直接在DOS执行RegSvr32命令,得到如下的用法提示:

注册一个COM组件的命令:regsvr32 c:

取消注册COM组件的命令:regsvr32 /u c:

注册一个组件或者取消注册一个组件,都会在注册表中有所反应。

其实,编译之后,VC已经在背后帮我们自动注册好了该COM组件。使用注册表编辑

查找AtlComSample,可以发现我们这个组件已经被注册好了,见下图:

注意其中CurVer下的一个字符串:ing.1它是我们引用这个组

件的ProgID。下面我们在VC中来使用这个组件。

6.另外新建一个MFC对话框工程MycomClient,并添加工程到当前工作区中:

7使用MFC类向导将上个工程中开发好的组件AtlComSample添加到当前工程中:类向导

Add ClassFrom a type library选择文件打开。

之后,你会发现在ClassView标签页下多了一个类:ICMyString。这个就是导入进来的

COM组件的类,其中包含了我们在上个工程中所写的StringTransfer函数。同时在项目目录

下多了2个文件:AtlComSample.h

下面我们就可以使用它了。

8.设计如下对话框界面:

关联如下变量:

对“字符串转换”按钮添加如下响应代码:

#include "atlcomsample.h" //别忘了加COM组件的头文件

void CMyComClientDlg::OnBtnTransfer()

{

ICMyString m_MyString;

//创建COM对象:这里的字符串一定要和注册表中的ProgID一致!!!

if(!m_Dispatch("ing.1"))

MessageBox("创建COM组件失败:请检查ProgID是否正确,组件是否已经注册");

UpdateData(true); //得到源字符串放到m_SourceString

BSTR bstrDestStr=SysAllocString(L"");

m_Transfer(m_SourceString,&bstrDestStr);//调用COM接口

m_DestString=bstrDestStr; //得到转换后结果字符串

UpdateData(false); //将结果字符串显示到编辑框中

}

运行结果如下:

总结:

1、用ATL开发COM组件:

作业:

1、参考上例,使用ATL建立一个COM组件接口MiniStringCom,实现2个接口函数:

First2Upper “将给定字符串首字母大写化” MergeString “将给定的2个字符串合并成1

个字符串”,并在客户程序中进行使用。

2、选修:在MSDN中查找“ATL Tutorial,即可找到微软提供的用ATL开发COM组件的

教程。按照该教程中的7个步骤,实现一个用多边形逼近圆的图形算法。有时间则翻译并讲

解。

3、选修:阅读文章《COM 组件设计与应用.mht