3.1 ATL 的基本特征
描述:ATL提供了实现基于COM组件内核的支持.下面是ATL所提供的一些功能
1)AppWizard,它负责创建起始的ATL工程
2)Object Wizard(对象向导),它为基本的COM组件创建代码
3)对低级别的COM功能的内置式支持,如IUnknown,类工厂和自注册(self-registration)功能
4)ATL支持Microsoft的接口定义语言(Interface Definition Language,IDL),它提供了对自定义的Vtable接口的调度支持,以及通过类型库进行自描述的功能
5)ATL支持IDispatch(自动化)和双向接口(dual-interface)
6)ATL可以支持开发效率更高的ActiveX控件
7)ATL提供对基本的视窗功能的支持
3.2 ATL 和 MFC
描述:ATL的目标是为了简化小规模的,基于COM组件的创建.MFC则基于Windows应用程序的开发速度,它通常带有大量的GUI.两者的相似之处是对OLE和ActiveX的支持
3.3 ATL 框架结构概述
描述:ATL是一个C++的类库或框架,它处理基于COM程序开发中的很多琐碎的例行工作,如,COM组件需要一个DLL或EXE宿主,而组件也需要一个类工厂.
3.3.1 ATL 的实现
1)ATL是一个基于模板的框架,它将ATL的实现作为你组件实现里的一部分.因此,你只需要在程序里包含头文件,即可获得ATL内建的功能.
2)ATL的设计目标是为了建立小型的,独立的组件.ATL提供了几个选项处理可执行文件变大的问题.如:
// stdafx.h
#define _ATL_APARTMENT_THREADED
#include <atlbase.h>
#include <atlcom.h>
....
// stdafx.cpp
#include "stdafx.h"
// include ATL's implementation code
#include <atlimpl.cpp>
// Yourimplementation.cpp
#include "stdafx.h"
stdafx文件包含了工程类型的基本要求,所以需要把它包含在你的实现文件里
通过ATLIMPL.CPP大部分ATL的实现都成为工程的一个显示部分,它的代码也变成你的可执行文件的一部分
ATL 的实现文件
文件名称 说明
ATLBASE.H ATL工程的基本包含文件
ATLCOM.H 所有的ATL工程都必须包含ATLCOM.H,因为该文件提供了ATL的大部分基本行为
ATLIMPL.CPP 在ATLBASE.H和ATLCOM.H里声明的类和方法的实现代码
ATLCTL.H,ATLCTL.CPP ATL对ActiveX控件所提供的支持,若使用ATL的控件支持,应包含它们
ATLWIN.H,ATLWIN.CPP ATL对视窗和对话框的支持
STATREG.H,STATREG.CPP ATL的注册组件的实现文件
ATLIFace.IDL,ATLIFACE.H ATL的注册器组件的支持文件,头文件里包含了输出,该输出是通过MIDL编译器运行IDL的结果
3)ATL实现一些基本的API是在一个名为ATL.DLL的模块里进行的.它提供ATL类的辅助函数.
4)如果定义了ATL_DLL_符号变量,工程将依赖于ATL.DLL文件.若没有,ATLIMPL.CPP文件将把实现作为模块的一部分包含进来
3.3.2 组件的宿主支持
1)ATL在它的CComModule类里封装了一个组件的宿主支持.ATL对开发者掩盖了这两种宿主类型(dll或exe)之间的大多数差别.
3.3.3 对IUnknown的支持
1)ATL的IUnknown实现对理解在实现中究竟应该使用哪个宏至关重要.
3.3.4 对类工厂的支持
1)CComClassFactory提供了对基本的类工厂的支持
2)CComClassFactory2对获得许可的类工厂提供支持
3)CComClassFactorySingleton为单元素类工厂提供支持
3.3.5 对COM程序开发的其他方面的支持
COM的功能项 ATL的支持类
ActiveX控件 CComControl,IOleControlImpl,IOleObjectImpl 等都支持控件开发
自动化(Automation) IDispatchImpl处理自动化和双向接口
COM的数据类型 CComBSTR 和 CComVariant
接口指针的管理 CComPtr 和 CComQIPtr
错误处理 ISupportErrorInfoImpl 和CComObject
连接点(Connection points) IConnectionPointContainerImpl 和 IConnectionPointImpl
异步属性下载 CBindStatusCallback
自注册(self-registration) ATL的注册对象(Register object,Iregistrar)提供自注册功能
视窗和对话框 CWindow,CWindowImpl,CDialogImpl,CMessageMap
3.4.4 接口
1)自定义(custom) -- 即标准的Vtable接口
2)双向(dual) -- 它既实现了一个Vtable接口,也实现了标准的自动化接口.
3.4.5 集合
1)集合(Aggregation)是一个COM技术,它使得一个组件可以兼并或复用组件的功能.
2)内部组件必须可以通过它的IUnknown实现来显示支持该技术
3.4.6 对ISupportErrorInfo的支持
1)提供一个具有健壮性的,服务器到客户机(server-to-client)的错误汇报机制.
3.4.7 对连接点的支持
1)连接点(Connection Points)使一个客户程序和服务器程序可以在对等的基础上彼此进行通讯
2.4.8 自由线程调度器
1)自由线程调度(Free-Threaded Marshaling)提供了在单进程的线程里以默认的方式对接口指针进行线程之间的参数调度
3.7 接口定义语言
描述: Microsoft的接口定义语言(Interface Definition Language,IDL)是基于DCE RPC规范之上的,通常IDL是用来描述远程进程调用接口(Remote Procedure Call Interface)的.但它包含了对基于COM接口的支持,IDL的主要职能是用来以一种与语言无关的方法来定义一个组件的接口(它的方法和参数),使客户可以使用该定义.
1)IDL使用一种C类型的语法,示例1
// Chapter3_Server.idl:IDL source for Chapter3_Server.dll
// The MIDL tool processes this file to
// produce the type library(Chapter3_Server.lib) and marshaling code
import "oaidl.idl";
import "ocidl.idl";
// 第一个接口
[
object,
uuid(8812899C-1CC9-11D1-883A-44455354000),
helpstring("IMath Interface"),
pointer_default(unique)
]
interface IMath:IUnknown
{
[helpstring("method Add")]
HRESULT Add(long lOp1,long lOp2,long *plResult);
[helpstring("method Subtract")]
HRESULT Substract(long lOp1,long lOp2,long *plResult);
[helpstring("method Multiply")]
HRESULT Multiply(long lOp1,long lOp2,long *plResult);
[helpstring("method Divide")]
HRESULT Divide(long lOp1,long lOp2,long *plResult);
};
// 第二个接口
[
object,
uuid(6AF3DF1E-C48F-11D0-A769-D477A4000000),
helpstring("IAdvancedMath Interface"),
pointer_default(unique)
]
interface IAdvancedMath:IUnknown
{
HRESULT Factorial([in] short sFact,[out,retval]long*pResult);
HRESULT Fibonacci([in] short sFib,[out retval]long *pResult);
};
// 链接库
[
uuid(A751FA9F-EE00-4733-8196-FDDA21B36F63),
version(1.0),
helpstring("ATLSERVER 1.0 Type Library")
]
library ATLSERVERLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
};
属性包括在括号([])之内,它们提供了关于后面定义语句的附加信息,比如IMath接口的定义开始于三个属性定义.
2)对象的属性指定了你所描述的是一个COM的自定义接口,而不是一个基于DCE/RPC的接口.IDL也可以描述RPC接口
[
object,
uuid(8812899C-1CC9-11D1-883A-44455354000),
helpstring("IMath Interface"),
pointer_default(unique)
]
3)uuid 指明你的接口的GUID,helpstring 指明了一些对象浏览器可以显示的文本.
4)pointer_default 属性为任何一个在接口内定义的指针设定了默认的指针属性.unique使指针所指向的内存不被应用程序的其他代码所更改.
5) 示例2:接口定义
interface IMath:IUnknown
{
[helpstring("method Add")]
HRESULT Add([in] long lOp1,[in] long lOp2,[out retval] long *plResult);
[helpstring("method Subtract")]
HRESULT Substract([in] long lOp1,[in] long lOp2,[out retval] long *plResult);
[helpstring("method Multiply")]
HRESULT Multiply([in] long lOp1,[in] long lOp2,[out retval] long *plResult);
[helpstring("method Divide")]
HRESULT Divide([in] long lOp1,[in] long lOp2,[out retval] long *plResult);
};
关键字 in 和 out 指定了参数传递的方向.关键字 retval 指明了参数应该被当作方法的返回值
6)示例3:类型库描述
[
uuid(8812698E-1CCB-11D1-883A-444553540000),
version(1.0),
helpstring("Chapter3_Server 1.0 Type Library")
]
library CHAPTER3_SERVERLIB
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
[
uuid(8812699D-1CC8-11D1-883A-444553540000),
helpstring("Math Class")
]
coclass Math
{
[default] interface IMath;
interface IAdvancedMath;
};
};
属性代码段从整体上描述了类型库,它有一个GUID,一个版本号,一个帮助字符串
关键字 library 指定了库的名称,并通常把指定的宿主文件的定义都包含进来,coclass 指定了独立的组件以及它们支持的接口
7) 基本的IDL关键字
关键字 说明
object 开始对一个基于COM的自定义接口进行定义,object 后面跟随了几个用于描述附加接口功能的属性
uuid 唯一定义给定接口,类型库或组件类的GUID
helpstring 指定一个处理组件和接口浏览器显示内容的字符串
interface 指定接口的名称.该名称随后被用在coclass部分里来指定组件支持的接口
coclass 描述又某一给定的COM对象所支持的接口.而GUID指定了组件本身.
default 指定了默认的组件接口.一个组件对象可以拥有至多两个默认接口.一个针对源,一个针对可编程宿接口
in/out/retval 它们针对方法的调用,它指定了每一个参数的传递方向.retval关键字说明了参数应该被当作方法的返回值处理
8)IDL文件经过MIDL编译器处理后产生的重要文件
a.编译IDL文件是为了生成C++头文件,在该头文件里声明并定义了接口,类标识符和工程的接口标识符.
b.IDL文件被编译成为二进制格式,即类型库.组件用户可以通过检查该类型库(.TLB)来确定他们该如何访问该库,并与之交互
c.IDL文件生成了一系列C文件和一个make文件,并可根据它们来创建代理/占位DLL文件.该DLL文件为组件的接口提供了参数调度方面的支持
3.8 ATL 工程的建立
3.8.1 ATL的基本宿主支持:CComModule
1)ATL的CComModule 类为 COM 对象提供了基本的宿主支持,每一个COM对象都必须在Windows进程中执行.
3.8.2 BEGIN_OBJECT_MAP 和 OBJECT_ENTRY 宏
1)这些宏建立起有个CLSID表和它们自己相关的ATL实现类(即组件)
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Main,CMath)
END_OBJECT_MAP()
2)ATL通过CLSID表,并使用宿主文件中每一个组件信息来刷新注册表.类的UpdateRegistry方法被调用.
3)宏DECLARE_REGISTRY自动实现了UpdateRegistry.
4)对象映射生成了某一组件的实例,即它提供了组件的类工厂.宏展开如下:
#define BEGIN_OBJECT_MAP(x) static _ATL_OBJMAP_ENTRY x[] = {
#define OBJECT_ENTRY(clsid,class) {
&clsid,
&class::UpdateRegistry,
&class::_ClassFactoryCreatorClass::CreateInstance, // 返回类工厂实例
&class::_CreatorClass::CreateInstance, // 返回组件类本身实例
NULL,0,&class::GetObjectDescription},
#define END_OBJECT_MAP() {NULL,NULL,NULL,NULL} };
3.8.3 CComModule
1)CComModule 位于ATLBASE.H 和 ATLIMPL.CPP 里.
常用的CComModule方法
方法名称 说明
Init 初始化CComModule实例,它通常对内部的数据成员进行初始化并使该实例处于就绪状态,它最后将调用AtlModuleInit()
GetClassObject 仅在DLL宿主文件里使用,它创建组件类工厂的一个实例.即它提供了DLL宿主文件的DllGetClassObject函数的实现
RegisterClassObject 被用来注册一个可执行宿主文件的类工厂
RevokeClassObject 当EXE文件被关闭的时候,Revoke方法可以用来注销类工厂
Lock/Unlock
RegisterServer 对出现在UnregisterServer宿主文件里的每一个组件添加或删除注册表项
3.8.4 ATL对组件的支持
1)每一个COM对象都必须支持IUnknown接口和其他公开其特定功能的接口
2)每一个COM对象也必须提供一个类工厂,从而客户应用程序才可以创建它
3)COM对象应该提供自注册功能
3.8.4.1 ATL对IUnknown的支持
3.8.4.2 CComObjectRootEx 和 CComObjectRootBase
1) 每一个将成为COM对象的ATL类都必须从CComObjectRootEx类里派生出来.
2)CComObjectRootEx间接地为组件提供了引用计数和QueryInterface支持
3)
class CComObjectRootBase
{
public:
CComObjectRootBase()
{
m_dwRef = 0L;
}
static HRESULT WINAPI InternalQueryInterface(void * pThis,
const _ATL_INTMAP_ENTRY * pEntries,
REFIID iid,void ** ppvObject)
{
HRESULT hRes = AtlInternalQueryInterface(pThis,pEntries,id,ppvObject);
}
ULONG OuterAddRef()
{
return m_pOuterUnknown->AddRef();
}
ULONG OuterRelease()
{
return m_pOuterUnknown->Release();
}
HRESULT OuterQueryInterface(REFIID iid,void **ppvObject)
{
return m_pOuterUnknown->QueryInterface(iid,ppvObject);
}
union
{
long m_dwRef;
IUnknown* m_pOuterUnknown;
};
};
CComObjectRootBase 包含了引用计数变量,用来维护对象的外部接口引用
4)一个集合实例也必须要通过IUnknown方法来调用它的外部组件.CComObjectRootBase对组件里的集合提供了基本的支持
5)CComObjectRootEx为非集合的IUnknown方法提供了支持.
template <class ThreadModel>
class CComObjectRootEx:public CComObjectRootBase
{
public:
typedef ThreadModel _ThreadModel;
typedef _ThreadModel::AutoCriticalSection _CritSec;
ULONG InternalAddRef()
{
return _ThreadModel::Increment(&m_dwRef);
}
ULONG InternalRelease()
{
return _ThreadModel::Decrement(&m_dwRef);
}
void Lock() { m_critsec.Lock();}
void Unlock() {m_critsec.Unlock();}
private:
_CritSec m_critsec;
}
3.8.4.3 CComObjectRoot 和线程管理
1)CComObjectRoot 定义
typedef CComObjectRootEx<CComObjectThreadModel> CComObjectRoot;
2)CComObjectThreadModel 的定义
// from ATLBASE.H
#if defined(_ATL_SINGLE_THREADED)
typedef CComSingleThreadModel CComObjectThreadModel;
typedef CComSingleThreadModel CComGlobalsThreadModel;
#elif defined(_ATL_APARTMENT_THREADED)
typedef CComSingleThreadModel CComObjectThreadModel;
typedef CComMultiThreadModel CComGlobalsThreadModel;
#else
typedef CComMultiThreadModel CComObjectThreadModel;
typedef CComMultiThreadModel CComGlobalsThreadModel;
#endif
3.8.5 AddRef 和 Release所在的位置
1)ATL通过CComObjectRootEx的内部和外部方法来管理引用的计数,CComObject中的类则处理对IUnknown方法实现
2)生成CMath类实例
IMath *pIMath = new CComObject<CMath>
3)实现math组件所必须的类
CComObjectBase
|
V
CComObjectRoot CComCoClass IMath
| | |
V V V
-----------------------------------------------
CMath
|
V
CComObject<CMath>
基本的ATL实现
CComObjectRoot 提供了InternalAddRef,InternalRelease,InternalQueryInterface
CComCoClass 提供了对类工厂的支持
IMath,IAdvancedMath 提供了抽象的接口类
以上这些组合在一起生成了CMath类,ATL更深一步,为了创建实例,它使用CMath作为CComObject的模板参数,使CComObject从CMath中派生出来
3.8.6 CComObject
// from atlcom.h
template <class Base>
class CComObject:public Base
{
public:
...
STDMETHOD_(ULONG,AddRef)(){return InternalAddRef();}
STDMETHOD_(ULONG,Release)()
{
ULONG l = InternalRelease();
if (l == 0)
delete this;
return l;
}
STDMETHOD(QueryInterface)(REFIID iid,void **ppvObject)
{
return InternalQueryInterface(iid,ppvObject);
}
};
注意这里的CComObject的方法直接调用了由CComObjectRootEx实现的内部方法,从而它们不能支持集合.
3.8.8 ATL_NO_VTABLE
1)ATL_NO_VTABLE 等同于 __declspec(novtable),它告诉编译器不要为类生成或初始化一个Vtable结构.
3.8.9 ATL的类工厂支持:CComCoClass
1)CComCoClass 提供了类工厂支持,以及获取CLSID和组件错误信息的基本方法
// from MATH.H
class ATL_NO_VTABLE CMath:
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMath,&CLSID_Math>,
public IMath,
public IAdvancedMath
{
...
};
上面既需要提供你的实现类的名称,也需要通过CComCoClass的模板参数来提供组件的CLSID
模板展开:
class CComCoClass
{
public:
DECLARE_CLASSFACTORY()
DECLARE_AGGREGATABLE(CMath)
typedef CMath _CoClass;
static const CLSID& WINAPI GetObjectCLSID(){return &CLSID_Math;}
static LPCTSTR WINAPI GetObjectDescription() {return NULL;}
static HRESULT WINAPI Error(LPCOLESTR lpszDesc,
const IID&iid = GUID_NULL,
HRESULT hRes =0)
{
return AtlReportError(GetObjecCLSID(),lpszDesc,iid,hRes);
}
...
};
2)CComCoClass 通过 DECLARE_CLASSFACTORY 为组件的类工厂提供支持.
3)ATL的OBJECT_ENTRY宏需要使用DECLARE_CLASSFACTORY.OBJECT_ENTRY宏为宿主文件中每一个组件建立一个构造函数映射表
4)DECLARE_CLASSFACTORY宏
#define DECLARE_CLASSFACTORY
typedef CComCreator<CComObjectCached<CComClassFactory>> \
_ClassFactoryCreatorClass;
5)DECLARE_AGGREGATABLE宏
#define DECLARE_AGGREGATABLE(x)
typedef CComCreator2<CComCreator<CComObject<x>>,\
CComCreator<CComAggObject<x>>> _CreatorClass;
6)_ClassFactoryCreatorClass 和 _CreatorClass 生成类包含了静态的CreateInstane方法用来创建类的实例
3.8.9.1 DECLARE_CLASSFACTORY 宏
// from ATLCOM.H
#define DECLARE_CLASSFACTORY()
DECLARE_CLASSFACTORY_EX(CComClassFactory)
#define DECLARE_CLASSFACTORY_EX(cf) \
typedef CComCreator<CComObjectCached<cf>> _ClassFactoryCreatorClass;
上面完全展开:
typedef CComCreator<CComObjectCached<CComClassFactory>> _ClassFactoryCreatorClass
1)ATL使用创建类来实际建立组件的实例,同时还使用创建类来建立组件的类工厂.
2)创建类通过CComObjectCached实现来建立起类工厂的实现,CComObjectCached的实现被存储在缓存里.
3.8.9.2 CComCreator
// from ATLCOM.H
template <class T1>
class CComCreator
{
public:
static HRESULT WINAPI CreateInstance(void *pv,REFIID riid,LPVOID * ppv)
{
_ASSERTE(*ppv == NULL);
HRESULT hRes = E_OUTOFMEMORY;
T1 *p = NULL;
p = new T1(pv);
if (p != NULL)
{
p->SetVoid(pv);
p->InternalFinalConstructAddRef();
hRes = p->FinalConstruct();
p->InternalFinalConstructRelease();
if (hRes == S_OK)
hRes = p->QueryInterface(riid,ppv);
if (hRes != S_OK)
delete p;
}
return hRes;
}
};
静态方法CreateInstance创建了被指定为模板参数的类的实例
3.8.9.3 CComClassFactory
class CComClassFactory : public IClassFactory,
public CComObjectRootEx<CComGlobalsThreadModel>
{
public:
BEGIN_COM_MAP(CComClassFactory)
COM_INTERFACE_ENTRY(IClassFactory)
END_COM_MAP()
//IClassFactory
STDMETHOD(CreateInstance)(LPUNKNOWN pUnkOuter,REFIID riid,void **ppvObj);
STDMETHOD(LockServer)(BOOL fLock);
//helper
void SetVoid(void *pv)
{
m_pfnCreateInstance = (_ATL_CREATORFUNC*)pv;
}
_ATL_CREATORFUNC* m_pfnCreateInstance;
};
m_pfnCreateInstance成员包含了一个指向其他创建类静态方法的指针,该指针为类工厂提供了组件的生成机制
3.8.9.4 CComCreator2 和 DECLARE_AGGREGATABLE
1)默认的CComCoClass为集合赋予了组件的支持
class CComCoClass
{
public:
typedef CComCreator<CComObjectCached<CComClassFactory>> _ClassFactoryCreatorClass;
DECLARE_AGGREGATABLE(CMath)
typedef CMath _CoClass;
...
};
2)DECLARE_AGGREGATABLE宏
#define DECLARE_AGGREGATABLE(x) public:\
typedef CComCreator2<CComCreator<CComObject<x>>,\
CComCreator<CComAggObject<x>>> _CreatorClass;
3)CComCreator2的定义
template <class T1,class T2>
class CComCreator2
{
public:
static HRESULT WINAPI CreateInstance(void *pv,REFIID riid,LPVOID *ppv)
{
_ASSERTE(*ppv == NULL);
HRESULT hRes = E_OUTOFMEMORY;
if (pv == NULL)
hRes = T1::CreateInstance(NULL,riid,ppv);
else
hRes = T2::CreateInstance(pv,riid,ppv);
return hRes;
}
};