void f(void) {
IUnknown *pUnk = 0;
// 调用
HRESULT hr = GetSomeObject(&pUnk);
if (SUCCEEDED(hr)) {
// 使用
UseSomeObject(pUnk);
// 释放
pUnk->Release();
}
}
void f(void) {
IUnknown *rgpUnk[3];
HRESULT hr = GetObject(rgpUnk);
if (SUCCEEDED(hr)) {
hr = GetObject(rgpUnk + 1);
if (SUCCEEDED(hr)) {
hr = GetObject(rgpUnk + 2);
if (SUCCEEDED(hr)) {
UseObjects(rgpUnk[0], rgpUnk[1],
rgpUnk[2]);
rgpUnk[2]->Release();
}
rgpUnk[1]->Release();
}
rgpUnk[0]->Release();
}
}
void f(void) {
IUnknown *rgpUnk[3];
ZeroMemory(rgpUnk, sizeof(rgpUnk));
if (FAILED(GetObject(rgpUnk)))
goto cleanup;
if (FAILED(GetObject(rgpUnk+1)))
goto cleanup;
if (FAILED(GetObject(rgpUnk+2)))
goto cleanup;
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
cleanup:
if (rgpUnk[0]) rgpUnk[0]->Release();
if (rgpUnk[1]) rgpUnk[1]->Release();
if (rgpUnk[2]) rgpUnk[2]->Release();
}
void f(void) {
IUnknown *rgpUnk[3];
ZeroMemory(rgpUnk, sizeof(rgpUnk));
__try {
if (FAILED(GetObject(rgpUnk))) leave;
if (FAILED(GetObject(rgpUnk+1))) leave;
if (FAILED(GetObject(rgpUnk+2))) leave;
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
} __finally {
if (rgpUnk[0]) rgpUnk[0]->Release();
if (rgpUnk[1]) rgpUnk[1]->Release();
if (rgpUnk[2]) rgpUnk[2]->Release();
}
CComPtr<IUnknown> unk;
CComPtr<IClassFactory> cf;
void f(IUnknown *pUnk1, IUnknown *pUnk2) {
// 如果非空,构造函数调用pUnk1的AddRef
CComPtr unk1(pUnk1);
// 如果非空,构造函数调用unk1.p的AddRef
CComPtr unk2 = unk1;
// 如果非空,operator= 调用unk1.p的Release并且
//如果非空,调用unk2.p的AddRef
unk1 = unk2;
//如果非空,析构函数释放unk1 和 unk2
}
void f(IUnknown *pUnkCO) {
CComPtr cf;
HRESULT hr;
// 使用操作符 & 获得对 &cf.p 的存取
hr = pUnkCO->QueryInterface(IID_IClassFactory,(void**)&cf);
if (FAILED(hr)) throw hr;
CComPtr unk;
// 操作符 -> 获得对cf.p的存取
// 操作符 & 获得对 &unk.p的存取
hr = cf->CreateInstance(0, IID_IUnknown, (void**)&unk);
if (FAILED(hr)) throw hr;
// 操作符 IUnknown * 返回 unk.p
UseObject(unk);
// 析构函数释放unk.p 和 cf.p
}
void f(void) {
CComPtr<IUnknown> rgpUnk[3];
if (FAILED(GetObject(&rgpUnk[0]))) return;
if (FAILED(GetObject(&rgpUnk[1]))) return;
if (FAILED(GetObject(&rgpUnk[2]))) return;
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
}
CComQIPtr<IDataObject, &IID_IDataObject> do;
CComQIPtr<IPersist, &IID_IPersist> p;
void f(IPersist *pPersist) {
CComQIPtr<IPersist, &IID_IPersist> p;
// 同类赋值 - AddRef''s
p = pPersist;
CComQIPtr<IDataObject, &IID_IDataObject> do;
// 异类赋值 - QueryInterface''s
do = pPersist;
}
CComPtr<IUnknown> unk;
CComQIPtr<IUnknown, &IID_IUnknown> unk;
void f(void) {
IFoo *pFoo = 0;
HRESULT hr = GetSomeObject(&pFoo);
if (SUCCEEDED(hr)) {
UseSomeObject(pFoo);
pFoo->Release();
}
}
void f(void) {
CComPtr<IFoo> pFoo = 0;
HRESULT hr = GetSomeObject(&pFoo);
if (SUCCEEDED(hr)) {
UseSomeObject(pFoo);
pFoo->Release();
}
}
注意CComPtr 和 CComQIPtr输出所有受控接口成员,包括AddRef和Release。可惜当客户端通过操作符->的结果调用Release时,智能指针很健忘 ,会二次调用构造函数中的Release。显然这是错误的,编译器和链接器也欣然接受了这个代码。如果你运气好的话,调试器会很快捕获到这个错误。
使用ATL智能指针的另一个要引起注意的风险是类型强制转换操作符对原始指针提供的访问。如果隐式强制转换操作符的使用存在争议。当 ANSI/ISO C++ 委员会在决定采用某个C++串类时,他们明确禁止隐式类型转换。而是要求必须显式使用c_str函数在需要常量char *(const char *)的地方传递标准C++串。ATL提供了一种隐含式的类型转换操作符顺利地解决了这个问题。通常,这个转换操作符可以根据你的喜好来使用,允许你将智能指针传递到需要用原始指针的函数。
void f(IUnknown *pUnk) {
CComPtr unk = pUnk;
// 隐式调用操作符IUnknown *()
CoLockObjectExternal(unk, TRUE, TRUE);
}
HRESULT CFoo::Clone(IUnknown **ppUnk) {
CComPtr unk;
CoCreateInstance(CLSID_Foo, 0, CLSCTX_ALL,
IID_IUnknown, (void **) &unk);
// 隐式调用操作符IUnknown *()
*ppUnk = unk;
return S_OK;
}
interface IMessageSource : IUnknown {
HRESULT GetNextMessage([out] OLECHAR **ppwsz);
}
interface IPager : IUnknown {
HRESULT SendMessage([in] const OLECHAR *pwsz);
}
interface IPager2 : IPager {
HRESULT SendUrgentMessage(void);
}
class CPager : public IMessageSource, public IPager2 {
LONG m_dwRef;
public:
CPager(void) :
m_dwRef(0) {}
virtual ~CPager(void) {}
STDMETHODIMP
QueryInterface(REFIID,
void**);
STDMETHODIMP_(ULONG)
AddRef(void);
STDMETHODIMP_(ULONG)
Release(void);
STDMETHODIMP GetNextMessage(OLECHAR **ppwsz);
STDMETHODIMP SendMessage(const COLECHAR * pwsz);
STDMETHODIMP SendUrgentMessage(void);
};
STDMETHODIMP_(ULONG) CPager::AddRef() {
return ++m_dwRef;
}
STDMETHODIMP_(ULONG) CPager::Release(){
ULONG result = -m_dwRef;
if (result == 0)
delete this;
return result;
}
STDMETHODIMP_(ULONG) CPager::AddRef() {
return InterlockedIncrement(&m_dwRef);
}
STDMETHODIMP_(ULONG) CPager::Release(){
ULONG result = InterlockedDecrement(&m_dwRef);
if (result == 0)
delete this;
return result;
}
STDMETHODIMP CPager::QueryInterface(REFIID riid, void **ppv) {
if (riid == IID_IUnknown)
*ppv = (IMessageSource*)this;
else if (riid == IID_IMessageSource)
*ppv = (IMessageSource*)this;
else if (riid == IID_IPager)
*ppv = (IPager*)this;
else if (riid == IID_IPager2)
*ppv = (IPager2*)this;
else
return (*ppv = 0), E_NOINTERFACE;
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
/D _ATL_SINGLE_THREADED
/D _ATL_APARTMENT_THREADED
/D _ATL_FREE_THREADED
ATL类型定义 | _ATL_SINGLE_THREADED | _ATL_APARTMENT_THREADED | _ATL_FREE_THREADED |
CComGlobalsThreadModel | CComSingleThreadModel | CComMultiThreadModel | CComMultiThreadModel |
CComObjectThreadModel | CComSingleThreadModel | CComSingleThreadModel | CComMultiThreadModel |
只要给定了上述的线程模型类型层次,你就能将相应的参数化线程行为添加到任何COM类。请看下列代码:
参数化的线程
class CPager : public IPager {
LONG m_dwRef;
typedef CComObjectThreadModel _ThreadModel;
_ThreadModel::CComAutoCriticalSection m_critsec;
: : : :
STDMETHODIMP_(ULONG) CPager::AddRef() {
return _ThreadModel::Increment(&m_dwRef);
}
STDMETHODIMP_(ULONG) CPager::Release(){
ULONG res = _ThreadModel::Decrement(&m_dwRef);
if (res == 0)
delete this;
return res;
}
STDMEHTHODIMP SendUrgentMessage() {
// 保证只有一个线程
m_critsec.Lock();
// 实现任务
this->GenerateMessage();
this->WakeUpUser();
// 允许其它线程
m_critsec.Unlock();
return S_OK;
}
};
typedef CComObjectThreadModel _ThreadModel;
typedef CComMultiThreadModelNoCS _ThreadModel;
union {
long m_dwRef;
IUnknown *m_pOuterUnknown;
};
static HRESULT WINAPI
CComObjectRootBase::InternalQueryInterface(void *pThis,
const _ATL_INTMAP_ENTRY *pEntries,
REFIID riid, void **ppv);
struct _ATL_INTMAP_ENTRY {
const IID* piid; // 接口ID (IID)
DWORD dw; // 多用途值
HRESULT (*pFunc)(void*, REFIID, void**, DWORD);
};
这个结构的第三个成员pFunc的取值有三种情况。如果pFunc等于常量_ATL_SIMPLEMAPENTRY,则结构成员dw为对象偏移量,这时不需要函数调用,并且InternalQueryInterface完成下列操作:
(*ppv = LPBYTE(pThis) + pEntries[n].dw)->AddRef();
return pEntries[n].pFunc(pThis, riid, ppv,
pEntries[n].dw);
class CPager
: public IMessageSource, public IPager2,
public CComObjectRootEx<CComMultiThreadModel>{
public:
CPager(void) {}
virtual ~CPager(void) {}
BEGIN_COM_MAP(CPager)
COM_INTERFACE_ENTRY(IMessageSource)
COM_INTERFACE_ENTRY(IPager2)
COM_INTERFACE_ENTRY(IPager)
END_COM_MAP()
STDMETHODIMP GetNextMessage(OLECHAR **ppwsz);
STDMETHODIMP SendMessage(const COLECHAR * pwsz);
STDMETHODIMP SendUrgentMessage(void);
};
{ &IID_IMessageSource, 0, _ATL_SIMPLEMAPENTRY },
{ &IID_IPager2, 4, _ATL_SIMPLEMAPENTRY },
{ &IID_IPager, 4, _ATL_SIMPLEMAPENTRY},
{ 0, 0, 0 }
class CPager
: public CComObjectRootEx,
public IPager,
public IObjectWithSiteImpl
{
public:
BEGIN_COM_MAP(CPager)
COM_INTERFACE_ENTRY(IPager)
COM_INTERFACE_ENTRY_IMPL(IObjectWithSite)
END_INTERFACE_MAP()
STDMETHODIMP SendMessage(const COLECHAR * pwsz);
};
template <
class T, // 双接口
const IID* piid, // 双接口IID
const GUID* plibid, // 包含类型库TypeLib
WORD wMajor = 1, // 类型库的版本
WORD wMinor = 0, //类型库的版本
class tihclass = CComTypeInfoHolder
>
class IDispatchImpl : public T { ... };
假设两个接口是DIPager 和 DIMessageSource。这个类的使用如下:
class CPager
: public CComObjectRootEx<CComMultiThreadModel>,
public IDispatchImpl<DIMessageSource,
&IID_DIMessageSource, &LIBID_PagerLib>,
public IDispatchImpl<DIPager,
&IID_DIPager, &LIBID_PagerLib>
{
public:
BEGIN_COM_MAP(CPager)
COM_INTERFACE_ENTRY(DIMessageSource)
COM_INTERFACE_ENTRY(DIPager)
// 下一个接口指定DIPager为缺省 [default]
COM_INTERFACE_ENTRY2(IDispatch, DIPager)
END_INTERFACE_MAP()
STDMETHODIMP SendMessage(BSTR pwsz);
STDMETHODIMP GetNextMessage(BSTR *ppwsz);
};
不要过分抽象
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 |
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);
};
IPager *p = new CComObjectNoLock();
输出你的类
实现了 CComObject ,你就有足够的条件用 C++ new 操作符创建 COM 对象。不过这样做没有什么实用价值,因为毕竟外部客户端使用 CoCreateInstance 或 CoGetClassObject 创建类实例。也就是说,你必须为每个外部类输出类对象。幸运的是ATL分别在它的 CComClassFactory 和 CComClassFactory2 类中提供了缺省的 IClassFactory 和 IClassFactory2接口实现。
CComClassFactory 不是模板驱动类,但其中有一个函数指针作为数据成员,使用这个函数可以创建对象。ATL提供了一个类模板家族,它们都有一个单独的静态方法 CreateInstance,由 Creators 调用,Creators 提供正确的语义来从 CComClassFactory 创建基于 CComObjectRoot 的对象。下面的这段代码展示了缺省的创建机制:CComCreator,它产生一个模板化的类实例,并用 ATL 中标准的 FinalConstruct 来顺序初始化对象。
ATL Creator
template class CComCreator {
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) {
HRESULT hRes = E_OUTOFMEMORY;
T1* p = NULL;
ATLTRY(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;
}
};
template class CComFailCreator {
public:
static HRESULT WINAPI CreateInstance(void*, REFIID,
LPVOID*)
{ return hr; }
};
template class CComCreator2 {
public:
static HRESULT WINAPI CreateInstance(void* pv, REFIID riid,
LPVOID* ppv) {
HRESULT hRes = E_OUTOFMEMORY;
if (pv == NULL)
hRes = T1::CreateInstance(NULL, riid, ppv);
else
hRes = T2::CreateInstance(pv, riid, ppv);
return hRes;
}
};
DECLARE_PROTECT_FINAL_CONSTRUCT()
struct _ATL_OBJMAP_ENTRY {
const CLSID* pclsid;
HRESULT (*pfnUpdateRegistry)(BOOL bRegister);
HRESULT (*pfnGetClassObject)(void* pv,
REFIID riid, LPVOID* ppv);
HRESULT (*pfnCreateInstance)(void* pv,
REFIID riid, LPVOID* ppv);
IUnknown* pCF;
DWORD dwRegister;
LPCTSTR (* pfnGetObjectDescription)(void);
};
class CPager
: public CComObjectRootEx,
public CComCoClass,
public IPager
{
public:
BEGIN_COM_MAP(CPager)
COM_INTERFACE_ENTRY(IPager)
END_INTERFACE_MAP()
STDMETHODIMP SendMessage(const OLECHAR * pwsz);
};
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Pager, CPager)
OBJECT_ENTRY(CLSID_Laptop, CLaptop)
END_OBJECT_MAP()
静态成员函数从 CComCoClass 派生,被隐含式定义。以上定义的对象映射一般通过使用 CComModule 的 Init 方法被传递到ATL:
_Module.Init(ObjectMap, hInstance);
DECLARE_NOT_AGGREGATABLE(CPager)
DECLARE_ONLY_AGGREGATABLE(CPager)
DECLARE_POLY_AGGREGATABLE(CPager)
ATL和注册表
CComModule 提供了两个方法用于自注册:一个是RegisterServer,另外一个是 UnregisterServer。这两个方法使用传递到 Init 例程的对象映射来完成实际的工作。正像我前面所提到的那样,每一个对象映射入口都包含 pfnUpdateRegistry 函数指针,这个指针必须由类实现者提供。ATL最初的版本所提供的例程为 CLSID 自动添加标准注册入口,从而使缺省行为的实现很容易。可惜这些例程不具备很好的可扩展性,而且如果服务器的需求超过了正常 InprocServer32 入口所包含的内容的话,就必须自己用手工来编写注册代码。
随着组件种类(categories)和 AppIDs 概念的出现,几乎就再没有服务器能认可由ATL1.0提供的标准注册入口。在ATL1.1及以后的版本中,首选的自注册技术是使用注册脚本,它非常灵活。这个技术需要 IRegistrar 接口的COM实现,它既可以静态链接以降低依赖性,也可以用 CoCreateInstance 动态绑定来最小化代码尺寸。
注册脚本只是个文本文件,它列出必须为给定的 CLSID 添加什么入口。注册脚本文件默认的扩展名为RGS,并作为定制的 REGISTRY 类型资源被添加进可执行文件。注册脚本的语法十分简单,归纳起来为:
[NoRemove|ForceRemove|val] Name [ = s|d ''''Value'''']
{
... 用于子键的脚本条目
}
REGEDIT4
[HKEY_CLASSES_ROOT\CLSID\{XXX}]
@=My Class
[HKEY_CLASSES_ROOT\CLSID\{XXX}\InprocServer32]
@=C:\foo\bar.dll
ThreadingModel=Free
HKCR {
NoRemove CLSID {
ForceRemove {XXX} = s ''''My Class'''' {
InprocServer32 = s ''''%MODULE%'''' {
val ThreadingModel = s ''''Free''''
}
}
}
}
class CPager : public
CComObjectRoot,public
IPager
CComCoClass {
DECLARE_REGISTRY_RESOURCEID(IDR_PAGER)
};
DateInstalled = s ''''%CURRENTDATE%''''
static HRESULT WINAPI
CPager::UpdateRegistry(BOOL b) {
OLECHAR wsz [1024]; SYSTEMTIME st;
GetLocalTime(&st);
wsprintfW(wsz, L"%d/%d/%d", st.wMonth, st.wDay,
st.wYear);
_ATL_REGMAP_ENTRY rm[] = {
{ OLESTR("CURRENTDATE"), wsz}, { 0, 0 },
};
return _Module.UpdateRegistryFromResource(IDR_PAGER,b, rm);
}
连接
COM 编程最单调乏味的一个方面是使用连接点来支持 outbound 接口。IConnectionPoint/IConnectionPointContainer 的设计好像是专门用来解决这个问题的,但是经验证明,不论是在性能方面,还是在易用性方面,它还是存在不足之处。ATL为每一个这样的接口提供了缺省的实现类,多少解决了一些易用性问题。
要理解ATL如何实现连结点,最容易的方式是看例子:假设定义了如下的 outbound 接口:
interface IPageSink : IUnknown {
HRESULT OnPageReceived(void);
}
interface IStopSink : IUnknown {
HRESULT OnShutdown(void);
}
class CPager
: public CComObjectRoot,
public CComCoClass,
public IPager,
public IConnectionPointContainerImpl,
public IConnectionPointImpl,
public IConnectionPointImpl
{
BEGIN_COM_MAP(CPager)
COM_INTERFACE_ENTRY(IPager)
COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CPager)
CONNECTION_POINT_ENTRY(IID_IPageSink)
CONNECTION_POINT_ENTRY(IID_IStopSink)
END_CONNECTION_POINT_MAP()
};
typedef IConnectionPointImpl base;
for (IUnknown** pp = base::m_vec.begin();
pp < base::m_vec.end();
pp++)
if (*pp)
((IPageSink*)(*pp))->OnPageRecieved();
class CPager
: public CComObjectRoot,
public CComCoClass,
public IPager,
public IConnectionPointContainerImpl,
public CProxyIPageSink,
public CProxyIStopSink
{
BEGIN_COM_MAP(CPager)
COM_INTERFACE_ENTRY(IPager)
COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CPager)
CONNECTION_POINT_ENTRY(IID_IPageSink)
CONNECTION_POINT_ENTRY(IID_IStopSink)
END_CONNECTION_POINT_MAP()
};
STDMETHODIMP CPager::SendMessage(LPCOLESTR pwsz) {
// send outbound notifications
HRESULT hr = Fire_OnPageRecieved();
// process normally
return hr;
}