用ATL建立轻量级的COM对象
第五部分
作者:赵湘宁
第一部分:为什么要使用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
是最容易理解的一个类。请看其代码:
template
class CComObjectNoLock : public Base {
public:
typedef Base _BaseClass;
CComObjectNoLock(void* = NULL){}
~CComObjectNoLock() {m_dwRef = 1L; FinalRelease();}
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);}
};
template
class CComObject : public Base {
public:
typedef Base _BaseClass;
CComObject(void* = NULL) { _Module.Lock(); }
~CComObject() {m_dwRef = 1L; FinalRelease(); _Module.Unlock();
}
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);}
static HRESULT WINAPI CreateInstance(CComObject** pp);
};
它假设你的对象将在堆中分配,也就是说最终调用
Release 时,将触发这个对象的 delete 操作。CComObjectNoLock
假设你的对象不是可聚合的,并且在服务器运行时,对象并不一直存在(因此有后缀
NoLock)。
为了在堆中分配基于 ATL 类的 CPager
实例,只要对这个类名进行 CComObject 模板包装即可:
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
终止主线程的消息循环。(待续)