http://www.cnblogs.com/devilmsg/archive/2008/08/31/1280513.html
深入解析ATL(第二版ATL8.0)(1.1-1.3节)
翻译:赖仪灵
出处: http://blog.youkuaiyun.com/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 宏的详细信息请参考第六章“接口映射”。