第一部分:为什么要使用ATL。
第二部分:起步篇。
第三部分:实现IUnknown。
第四部分:实现接口。
不要过分抽象
ATL最不直观的一个方面是你所定义和实现的C++类仍然是抽象基类。没错,在ATL的模板类和宏上辛苦了半天,却仍然得不到一个可以实例化的类。即使你从 CComObjectRootEx 派生,其结果同从一个或更多的ATL接口实现继承一样。从技术上讲,你的对象不提供 IUnknown 三个核心方法(QueryInterface,AddRef 和 Release)的实现。如果你检查现有ATL之前的 COM 实现,如果不是全部,那么也是大多数的方法实现并不在乎这个类是不是被用于COM聚合或tear-off,是不是被用于独立的对象或一个包含在内的数据成员,是不是要作为基于堆的对象或作为全局变量,以及是不是对象存在时,一直要保持服务器运行。为了允许最大限度的灵活性,所有这些方面分别通过ATL家族中的十个类属的 CComObject 之一来说明。参见下表:
类名 | 服务器是否加锁 | 是否代理IUnknown | 是否删除对象 | 备注 |
CComObject | Yes | No | Yes | 常规情况 |
CComObjectCached | Yes(在第二次AddRef之后) | No | Yes | 用于通过内部指针控制的对象 |
CComObjectNoLock | No | No | Yes | 用于不控制服务器运行的对象 |
CComObjectGlobal | Yes(在第一次AddRef之后) | No | No | 用于全程变量 |
CComObjectStack | No | No | No | 用于不能被增加引用计数的基于堆栈的变量 |
CComContainedObject | No | Yes | No | 用于MFC风格的嵌套类 |
CComAggObject | Yes | Yes | Yes | 仅用于聚合实现 |
CComPolyObject | Yes | Yes(如果聚合) | Yes | 用于聚合/非聚合实现 |
CComTearOffObject | No | Yes(仅用于QueryInterface) | Yes | 用于每次请求所创建的tear-offs |
CComCachedTearOffObject | No | Yes(通过第二个IUnknown) | Yes | 用于在第一次请求和缓存时所创建的tear-offs |
每一个 CComObject 类用派生来提供正确的 QueryInterface,AddRef 和 Release 实现。如果QueryInterface,AddRef 和 Release的语义正确,则所有 CComObject 类用你的类名作为模板参数并创建一个从你的类派生的新类。在这些类中,CComObjectNoLock 是最容易理解的一个类。请看其代码:
01.
template
02.
class
CComObjectNoLock :
public
Base {
03.
public
:
04.
typedef
Base _BaseClass;
05.
CComObjectNoLock(
void
* = NULL){}
06.
~CComObjectNoLock() {m_dwRef = 1L; FinalRelease();}
07.
08.
STDMETHOD_(
ULONG
, AddRef)() {
return
InternalAddRef();}
09.
STDMETHOD_(
ULONG
, Release)() {
10.
ULONG
l = InternalRelease();
11.
if
(l == 0)
12.
delete
this
;
13.
return
l;
14.
}
15.
STDMETHOD(QueryInterface)(REFIID iid,
void
** ppvObject)
16.
{
return
_InternalQueryInterface(iid, ppvObject);}
17.
};
18.
19.
template
20.
class
CComObject :
public
Base {
21.
public
:
22.
typedef
Base _BaseClass;
23.
CComObject(
void
* = NULL) { _Module.Lock(); }
24.
~CComObject() {m_dwRef = 1L; FinalRelease(); _Module.Unlock();
25.
}
26.
27.
STDMETHOD_(
ULONG
, AddRef)() {
return
InternalAddRef();}
28.
STDMETHOD_(
ULONG
, Release)() {
29.
ULONG
l = InternalRelease();
30.
if
(l == 0)
31.
delete
this
;
32.
return
l;
33.
}
34.
STDMETHOD(QueryInterface)(REFIID iid,
void
** ppvObject)
35.
{
return
_InternalQueryInterface(iid, ppvObject);}
36.
static
HRESULT
WINAPI CreateInstance(CComObject** pp);
37.
};
它假设你的对象将在堆中分配,也就是说最终调用 Release 时,将触发这个对象的 delete 操作。CComObjectNoLock 假设你的对象不是可聚合的,并且在服务器运行时,对象并不一直存在(因此有后缀 NoLock)。
为了在堆中分配基于 ATL 类的 CPager 实例,只要对这个类名进行 CComObject 模板包装即可:
1.
IPager *p =
new
CComObjectNoLock();
CComObjectNoLock 类从 CPager 派生,并且添加了由 CPager 提供的使用 InternalQueryInterface,InternalAddRef 和 InternalRelease 的 QueryInterface,AddRef 和 Release实现。因为此对象是基于堆的,所以对 delete 的调用将发生在 CComObjectNoLock 类的 Release 实现中,此时InternalRelease 返回零。
CComObject 类通常被用于基于堆的对象,这个对象只要存在,则服务器一直要运行。与许多 CComObject 家族中的其它类一样,CComObject提供了一个全程变量,_Module,它有两个方法,Lock 和 Unlock。这些方法与 MFC 的 AfxOleLockApp 和AfxOleUnLockApp 很相似。CComObject 的构造函数调用_Module 的 Lock 方法,而 CComObject 的析构函数则调用_Module的Unlock 方法。ATL提供了一个 CComModule 类,它以适当的方式为进程内服务器实现了这些方法。当建立进程外服务器时,某个派生类(CExeModule)必须改写默认的 Lock / Unlock方法,以便以某种适当的方式下掉服务器。基于 AppWizard 的 ATL工程自动会有一个类用 PostThreadMessage 终止主线程的消息循环。(待续)