翻译:赖仪灵
出处:
http://blog.csdn.net/laiyiling
声明:版权归原作者拥有,请勿随意转载此翻译文档,保留一切权利。
1.1
什么是
ATL
ATL
的全称并不能完整的说明
ATL
是什么,提供了什么功能。
Active
一词实际上是过去
Microsoft
市场时期的遗留物,当时的“
ActiveX
”表示
COM
。而现在,“
ActiveX
”表示控件。
ATL
对建立控件提供了非常强大的支持,但是它实际的功能远不止这些。
ATL
的主要功能:
1
、
对复杂的数据类型提供包装类,比如智能指针、
VARIANT(S)
、
BSTR(S)
、
HWND(S)
。
2
、提供一些基本
COM
接口的实现类,比如
IUnknown, IClassFactory, IDispatch, IPersistXxx, IConnectionPointContainer,
和
IEnumXxx
。
3
、提供
COM
服务器的管理类,比如暴露类对象、实现自我注册以及管理服务器生命期。
4
、提供建立
COM
控件和控件容器的支持类,以及建立旧的空白窗口程序。
5
、庞大的类库支持建立
WEB
应用程序和
XML Web Services
。
6
、快速建立应用程序的向导功能。
ATL
的实现启发于当今
C++
领域的类库标准——标准模板库(
STL
)。
ATL
由一些短小、高效、灵活的类组成。权利与职责同在,与
STL
一样,只有具有一定经验的
C++
程序员才能很好的使用
ATL
。
当然,我们的主要目的编写
COM
应用程序。使用、实现
COM
对象、
COM
服务器的经验也是要求的。对于没有任何
COM
知识、希望建立
COM
对象的人,
ATL
并不适合(这种情况其他的任何工具如
Visual Basic
、
MFC
都是不适合的)。使用
ATL
要求非常熟悉
C++
中
COM
如何实现,以及一些
ATL
本身的实现细节。
事实上,
ATL
也实现了一些向导帮助我们生成初始的代码。在本章的剩余部分,展示了在
Visual Studio 2005
中如何使用
ATL
向导。一起来体验吧!
1.2
创建
COM
服务器
1.2.1
创建
ATL
工程
Visual Studio
中任何开发的第一步就是建立解决方案,初始化工程。选择
File => New Project
菜单,显示图
1
-
1
所示的新建工程对话框,然后选择
Visual C++
工程类型。
图
1
-
1
创建新的
Visual Studio
工程
选择
Visual C++
工程文件夹显示所有可用的
C++
工程模板类型。工程名称(图
1-1
中是
PiSvr
)是生成的
DLL
或者
EXE
服务器名称。
ATL
工程模板的工作就是为你的
COM
服务器建立一个工程。
COM
服务器既可以是
DLL
也可以是
EXE
,
EXE
又可以进一步分为标准应用程序恶化
NT
服务。
ATL
工程模板支持所有的三种服务器类型。默认情况下,初始选择
DLL
服务器类型,如图
1-2
所示。
图
1-2 ATL
工程设置
1.2.2 ATL
工程向导选项
图
1-2
的工程向导中列出的部分选择我们需要做进一步的解释。第一个是选择工程建立使用
ATL
属性。正如在前言中所述,在
ATL 8
中首选使用非属性工程,所以本书也集中讨论非属性工程。如果你需要使用属性,请参考附录
D
的“
ATL
属性”,介绍了属性工程的知识。
在附加选择中,第一个选项允许绑定自定义的代理
/
存根代码到
DLL
服务器中。默认不选择。选中后,
Visual Studio
会为代理
/
存根生成一个单独的工程,名称为
<ProjectName>PS.vcproj
。同时添加到服务器的解决方案中。此工程编译生成一个代理
/
存根
DLL
,把这个
DLL
分发到所有需要列集和散列自定义接口的客户、服务器机器上。注意,在解决方案的默认编译配置中(
Debug, Release
),都没有选中代理
/
存根
DLL
的同时编译,在图
1-3
所示的解决方案属性页中,选中
PiSvrPS
工程最后
Build
下的选择框,然后在你编译解决方案的时候代理
/
存根
DLL
工程也会同时编译发布。
图
1-3
新建
ATL COM
服务器的应用程序设置
如果希望把代理
/
存根
DLL
捆绑到服务器
DLL
一起(要求服务器安装在客户计算机),可以把
Allow Merging of Proxy/Stub Code
选项选中(非属性工程)。这样做以后解决方案就只有一个单独的工程(主服务器工程),同时为了合并代理
/
存根
DLL
的代码,部分条件编译语句会添加到服务器工程中。预编译符号
_MERGE_PROXYSTUB会控制是否把
代理
/
存根的代码编译到服务器,这个定义符号默认会添加到所有的配置文件中。
如果没有足够的理由(已经超出了本书的讨论范围),应该尽量避免把代理
/
存根代码插入到服务器中,使用双重接口或者
OLE
自动化兼容的自定义接口更合适。
ATL
工程向导的第二个选项可以给工程添加微软基础类库(
MFC
)支持。坦白说,应该避免选择这个选项。下面列举了一些缺陷,说明开发人员为什么应该关闭这个选项:
1
、离开
CString
(
CMap CList
等等),我就无法工作。
MFC
工具类库只是在
C++
标准委员会建立标准程序库之前的临时产品。在标准程序库建立之后,应该停止使用
MFC
版本,因为标准程序库提供的类
string, map, list
等,与等价的
MFC
版本相比更灵活健壮。而且,现在的
CString
类是
MFC
和
ATL
的共享类,因此在
ATL
最新版中不需要额外包含其他的
MFC
部分,也不需要链接
MFC
库,可直接使用。其他的
MFC
工具类也称为了共享类,如
CPoint
和
CRect
。对于这些和
MFC
类似的集合类库,在
ATL
最新版实现了相应的集合类(
CAtlMap, CAtlList
等等)。
2
、没有向导就不能工作。本章的内容全部是关于
Visual Studio
提供给程序员的向导功能。
ATL
的向导功能和
MFC
的一样强大。
3
、我已经掌握
MFC
,不想学其他的。幸运的是,有这样思想的人现在都不会读本书。
ATL
工程向导的第三个选项是支持
COM+
,使得工程链接
COM+
组件服务库
comsvs.dll
,包含相应的头文件
comsvcs.h
。所有应用程序就可以访问
COM+
的各种接口。选中
COM+
支持后,可以选中子项以支持组件注册,在工程中生成额外的
coclass
类实现
IComponentRegister
接口。
1.2.3 ATL
工程向导的结果
不管有没有选中上面介绍的三个工程项,
ATL
向导生成的
COM
服务器都实现了如下的三个功能:自注册、服务器生命期控制、暴露类对象。作为额外提供的方便,向导在每次编译成功后发送一个事件以注册
COM
服务器。根据
DLL/EXE
服务器的类型,响应事件执行
regsvr32.exe <project>.dll
或者
<project>.exe /regserver
。
关于
COM
服务器支持此三项功能的更详细信息,以及如何扩展进行高级的同步控制和声明期需要,请参考第五章
COM
服务器。
1.3
插入
COM
类
1.3.1
添加
ATL
简单对象
建立了
ATL COM
服务器后,可能需要插入一个新的
COM
类。选择菜单
Project => Add Class Item
实现。插入
ATL
类时,需要线选中插入类的类型,如果
1-4
所示。
图
1-4
添加
ATL COM
类
如果时间充足,你也许想花点时候稍微的浏览一下可用的类类型。每个类型都会生成一系列特殊的代码,它们使用
ATL
基础类提供大部分的功能,然后生成自定义接口的框架。
COM
类实现不同的
COM
接口时,你可以选择不同向导类型。不幸的是,向导并没有提供访问所有
ATL
功能(甚至大部分功能)。但是,设计生成的代码很容易修改、开始之后很方便增加、删除函数。熟悉这些向导生成类的类型和选项最好的办法就是实践。
选择类类型后(点击
OK
),
Visual Studio
通常会要求输入一些其他的信息。一些类型的附加选项比
ATL
简单类型多很多(图
1-4
选择简单类型)。大部分的
COM
类都至少需要图
1-5
和图
1-6
所示的信息。
图
1-5
设置
COM
类名称
图
1-6
设置
COM
类名称
在
ATL
简单对象向导对话框的名称页中,只需要输入短名称,如
CalcPi
。短名称被用来组成对话框中的其他部分信息(你也可以选择不使用这些自动生成的信息)。这些信息分为两类:必要的
C++
信息,包括
C++
类名称、
C++
类的头文件、实现文件名称;必要的
COM
信息,包括
coclass
名称(接口定义语言使用
[IDL]
)、默认接口的名称(也用于
IDL
)、称为类型的友好名称(用于
IDL
和注册设置)、最后还有版本独立的程序标识符(用于注册设置)。版本相关的
ProgID
是版本独立的
ProgID
加上“
.1
”后缀组成。注意其中的
Attributed
选项不能选中。在非属性工程中,我可以有选择的添加属性化的类到非属性化的工程中,只需要选中图
1-5
中的
Attributed
项。
在
Options
页中,可以修改一些低级的
COM
设定。线程模型描述了你希望新增类的实例所生存的套间类型:单线程套间(
STA
,也称为套间模型);多线程套间(
MTA
,也称为自由套间模型)。
Single
模型通常用于少数的类,不管客户是怎样的套间模型,类的所有实例都要求共享在应用程序的主
STA
中。
Both
模型使对象和客户处于相同的套间类型,这样可以避免过多的代理
/
存根对。中立线程模型只有在
Window 2000
及以后的操作系统才可以用。使对象可以在调用者的线程中安全执行。调用中立类型的组件通常速度更快,因为它不需要线程交换,而在不同类型的套间之间进行跨套间调用通常是需要线程交换的。你选择的线程模型设置会影响服务器在注册中存储的
ThreadingModel
值,它决定了对象应该如何实现线程安全的
AddRef
和
Release
。
接口类型设置允许你设定新增类的默认接口类型:
Custom
(需要自定义的代理
/
存根,不从接口
IDispatch
继承);
Dual
(使用类型库进行列集,从
IDispatch
继承)。此设置影响生成的
IDL
默认接口。选择
Custom
后可以进一步选中自动化兼容选项。选中后的
IDL
定义中会添加
[oleautomation]
修饰属性,它限制了接口方法的变量类型可以使用
OLE
自动化兼容类型。比如,
[oleautomation]
接口方法必须使用
SAFEARRAY
代替方便的
C
风格数组。如果你希望
ATL COM
对象适用于不同的客户环境,比如
Visual Basic
,你就应该选中这个选项。而且,如果使用了
[oleautomation]
接口,运行在微软
.Net
框架中的代码访问
COM
对象更简单。部署
[oleautomation]
接口的
COM
对象也可能更简单,因为此类接口总是使用统一的列集在不同的套间传递接口。在支持
COM
的计算机上总是存在类型库列集的,因此你不需要与组件一起发布额外的代理
/
存根
DLL
。
聚集设置允许你设定是否希望你的对象被聚集使用,也就是能否作为受控的内部对象参与聚合。这个设置不会影响当前新增类对象是否可以作为外部控制对象使用聚合。关于被聚合的更多信息请参考第四章的“
ATL
的对象”。关于如何聚合其他对象请参考第六章的“接口映射”。
支持
ISupportErrorInfo
设置指示向导生成一个
ISupportErrorInfo
接口的实现。如果希望抛出
COM
异常那么就有必要选中这个选项。在跨语言和套间边界时,与单独的
HRESULT
所提供的错误相比,
COM
异常(也称为
COM
错误信息对象)可以传递更多的错误细节。关于抛出、捕捉
COM
异常的更多信息请参考第五章“
COM
服务器”。
支持连接点指示向导生成
IConnectionPoint
的实现,它允许对象激发脚本环境中的事件,比如浏览器的寄宿脚本。控件同样使用连接点激发控件容器的事件,详细参考第九章“连接点”。
在帮助信息中,
Free-Threaded Marshaler
的解释是:允许同一接口的客户获得一个原始接口,即使它们的线程模型不匹配。这个帮助信息并没有说使用这个选项的危险性。不辛的是,
Free-Threaded Marshaler
(
FTM
)如果代价高昂的汽车:虽然你想拥有,但你却负担不起。在选择此项前请先参考第六章“接口映射”关于
FTM
的描述。
支持
IObjectWithSite
设置使向导生成一个
IObjectWithSite
的实现。寄宿于容器(如
IE
浏览器)的对象需要使用这个接口。容器使用这个接口传递接口指针到它们容纳的对象,然后对象就可以直接和容器通信。
1.3.2 ATL
简单对象向导的结果
在设置好这些选项后,简单对象向导替你生成一个单独的文件,以开始增加自己的实现。对于此新增类:生成一个新的类定义头文件;一个新的实现
CPP
文件;一个
.RGS
文件包含注册信息。而且
IDL
文件也会更新包含新接口的定义。
生成的类定义文件如下:
// CalcPi.h :
声明
CCalcPi
#pragma once
#include "resource.h" //
主要资源符号
#include "PiSvr.h"
#include "_ICalcPiEvents_CP.h"
class ATL_NO_VTABLE CCalcPi :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CCalcPi, &CLSID_CalcPi>,
public ISupportErrorInfo,
public IConnectionPointContainerImpl<CCalcPi>,
public CProxy_ICalcPiEvents<CCalcPi>,
public IDispatchImpl<ICalcPi, &IID_ICalcPi, &LIBID_PiSvrLib,
/*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CCalcPi() { }
DECLARE_REGISTRY_RESOURCEID(IDR_CALCPI)
BEGIN_COM_MAP(CCalcPi)
COM_INTERFACE_ENTRY(ICalcPi)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CCalcPi)
CONNECTION_POINT_ENTRY(__uuidof(_ICalcPiEvents))
END_CONNECTION_POINT_MAP()
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct() {
return S_OK;
}
void FinalRelease() {
}
public:
};
OBJECT_ENTRY_AUTO(__uuidof(CalcPi), CCalcPi)
第一个需要注意的就是基类列表。此例中,
ATL
同时使用了模板和多继承技术。每个基类都提供了
COM
对象需要的一些普通实现代码。
1
、
CComObjectRootEx
提供了
IUnknown
接口实现
2
、
CComcoClass
提供了类厂实现
3
、
ISupportErrorInfo
是一个接口,实现了
CPP
文件的一个方法。
4
、选中了支持连接点选项后,
IConnectionPointContainerImpl
提供了此功能的实现
5
、
CProxy_ICalcPiEvents
也是连接点的部分实现
6
、
IDispatchImpl
提供了对象双接口
IDispatch
的实现。
另一个需要注意的是
COM_MAP
宏,它是
ATL
映射的一个实例:一系列生成代码的宏(通常用来生成查找表)。特别的是
COM_MAP
宏用来实现所有
COM
对象都要求支持的
QueryInterface
方法。
关于
ATL
基类如何实现基本
COM
功能,如何利用这些实现建立对象层次以及适当的同步多线程对象,请参考第四章“
ATL
对象”。如何使用
COM_MAP
宏的详细信息请参考第六章“接口映射”。