`
阿尔萨斯
  • 浏览: 4170729 次
社区版块
存档分类
最新评论

ATL智能指针类剖析

 
阅读更多

CComPtr

CComPtr派生自模板类CComPtrBase<T>,T代表某个COM接口。CComPtrBase<T>类只有一个成员变量T* p。下面是它们的代码分析:

构造函数

protected:
CComPtrBase() throw()
{
p = NULL;
}
CComPtrBase(_In_ int nNull) throw()
{
ATLASSERT(nNull == 0);
(void)nNull;
p = NULL;
}
CComPtrBase(_In_opt_ T* lp) throw()
{
p = lp;
if (p != NULL)
p->AddRef();
}

构造函数都被限定为protected,这样保证了无法直接创建CComPtrBase<T>对象,必须有子类的构造函数来调用它们。默认构 造函数和整数0作为参数的构造函数将p初始化为NULL。第三种形式会保存外部接口指针给p,并调用AddRef。

CComPtrBase没有提供AddRef成员函数供外部使用,为了生命周期一致,这是必要的。所以也不要通过operator->来调用内部p的AddRef方法。

CComPtr<T>增加了拷贝构造函数:

CComPtr(_In_ const CComPtr<T>& lp) throw() :
CComPtrBase<T>(lp.p)
{
}

本质上还是调用基类的第三种构造函数,因此也会调用p->AddRef方法。

析构函数

public:
~CComPtrBase() throw()
{
if (p)
p->Release();
}

注意析构函数没有virtual关键字,按照我们通常的认识,一个类如果被设计成被别的类继承,它需要一个虚析构函数,但是这里没有。为什么呢?因为它的子类CComPtr<T>没有任何成员变量,因此当我们如下调用的时候,并不会造成内存泄露。

CComPtrBase<IUnknown>* p=new CComPtr<IUnknown>();
delete p;

但是,这并不是一个很好的方法,虽然看似这里省了点虚函数调用的开销,但是谁又能保证不会有哪个人突然自己创造了一个 CComPtrBase<T>的子类,然后加了很多自己的成员变量,而如果他没有看过CComPtrBase<T>的代码,结果 会怎么样?我认为这的确是个设计不良!

析构函数保证了退栈的时候,如果p不为NULL,则会被调用接口的Release函数。CComPtr<T>没有提供析构函数,由于没有成员变量拥有析构函数,父类也没有虚析构函数,所以编译器也不会生成默认析构函数。

Release

作为一个原则,要记住的是我们总应该调用CComPtrBase::Release,而不是通过operator ->获得的接口指针来调用Release方法。为什么呢?再看看上面的代码,析构函数在p不为NULL不为的时候,会调用Release。如果我们 直接通过->来调用Release,我们又忘记将成员变量p设置为NULL设置为的话,那么析构函数会进行第二次Release调用,结果当然会导 致错误。下面是可靠地CComPtrBase::Release方法的代码:
void Release() throw()
{
T* pTemp = p;
if (pTemp)
{
p = NULL;
pTemp->Release();
}
}

为什么不直接写成p->Release;p=NULL;而要引入一个pTemp变量呢,这是因为如果这样的话,在下面情况下,会出现两次调用接口的Release方法:
1)假设B类对象又拥有一个A类对象的指针作为成员变量pA,而A类对象pA又有一个成员变量CComPtr<T> sp指向B类对象;
2) 当用户调用pA->sp.Release方法,如果Release实现代码是这样:
if(p)
{
p->Release;
p=NULL;
}
当执行到p->Release时,也就是调用B对象的Release方法;
3)假设B::Release方法内部释放了A对象(delete pA),也就是导致A对象的析构函数被调用
4)A对象的析构函数的调用又导致了A对象内部CComPtr<T> sp的析构函数被调用
5)CComPtr<T> sp的Release方法被再次调用,这时候sp.p仍然不为NULL,因此p->Release会被调用第二次,崩溃了。

AddRef和Release保险机制


下面是个小技巧,懒得翻译了,直接拷贝自"ATL Internals"第二版。

_NoAddRefReleaseOnCComPtr<T>* operator->() const {
ATLASSERT(p!=NULL); return (_NoAddRefReleaseOnCComPtr<T>*)p;
}

This is a simple template class whose only purpose is to make the AddRef and Release methods inaccessible:

template <class T>
class _NoAddRefReleaseOnCComPtr : public T
{
private:
STDMETHOD_(ULONG, AddRef)()=0;
STDMETHOD_(ULONG, Release)()=0;
};

The _NoAddRefReleaseOnCComPtr<T> template class derives from the interface being returned. Therefore, it inherits all the methods of the interface. The class then overrides the AddRef and Release methods, making them private and purely virtual. Now you get the following compiler error when you use the arrow operator to call either of these methods:

error C2248: 'Release' : cannot access private member declared
in class 'ATL::_NoAddRefReleaseOnCComPtr<T>'

operator =

CComPtrBase<T>没有提供赋值操作,CComPtr提供了下面几个:
T* operator=(_In_opt_ T* lp) throw()
{
if(*this!=lp)
{
return static_cast<T*>(AtlComPtrAssign((IUnknown**)&p, lp));
}
return *this;
}
template <typename Q>
T* operator=(_In_ const CComPtr<Q>& lp) throw()
{
if( !IsEqualObject(lp) )
{
return static_cast<T*>(AtlComQIPtrAssign((IUnknown**)&p, lp, __uuidof(T)));
}
return *this;
}
T* operator=(_In_ const CComPtr<T>& lp) throw()
{
if(*this!=lp)
{
return static_cast<T*>(AtlComPtrAssign((IUnknown**)&p, lp));
}
return *this;
}
相关的ATL函数如下:
ATLINLINE ATLAPI_(IUnknown*) AtlComPtrAssign(_Inout_opt_ _Deref_post_opt_valid_ IUnknown** pp, _In_opt_ IUnknown* lp)
{
if (pp == NULL)
return NULL;

if (lp != NULL)
lp->AddRef();
if (*pp)
(*pp)->Release();
*pp = lp;
return lp;
}

TLINLINE ATLAPI_(IUnknown*) AtlComQIPtrAssign(_Inout_opt_ _Deref_post_opt_valid_ IUnknown** pp, _In_opt_ IUnknown* lp, REFIID riid)
{
if (pp == NULL)
return NULL;

IUnknown* pTemp = *pp;
*pp = NULL;
if (lp != NULL)
lp->QueryInterface(riid, (void**)pp);
if (pTemp)
pTemp->Release();
return *pp;
}

bool IsEqualObject(_In_opt_ IUnknown* pOther) throw()
{
if (p == NULL && pOther == NULL)
return true; // They are both NULL objects

if (p == NULL || pOther == NULL)
return false; // One is NULL the other is not

CComPtr<IUnknown> punk1;
CComPtr<IUnknown> punk2;
p->QueryInterface(__uuidof(IUnknown), (void**)&punk1);
pOther->QueryInterface(__uuidof(IUnknown), (void**)&punk2);
return punk1 == punk2;
}

主要功能都是:
1)先判断两个接口是否相同,不同则继续
2)如果p不为NULL,调用p->Release
3)将接口直接赋值给p或者通过接口查询方式初始化p
4)保证接口被赋值后AddRef被调用

Attach和Detach方法

void Attach(_In_opt_ T* p2) throw()
{
if (p)
p->Release();
p = p2;
}
// Detach the interface (does not Release)
T* Detach() throw()
{
T* pt = p;
p = NULL;
return pt;
}

Attach方法先释放原先p所指接口,然后接受新接口,注意,不会导致AddRef调用。
Detach方法将p设置为NULLL,然后返回接口。如果外部调用需要保存返回的接口,否则就遗失了。

CopyTo

_Check_return_ HRESULT CopyTo(_Deref_out_opt_ T** ppT) throw()
{
ATLASSERT(ppT != NULL);
if (ppT == NULL)
return E_POINTER;
*ppT = p;
if (p)
p->AddRef();
return S_OK;
}
将自己的接口拷贝个参数指针,并调用AddRef。

operator*

T& operator*() const
{
ATLENSURE(p!=NULL);
return *p;
}

有了这个操作,我们就可以使得CComPtr拥有和普通指针同样的行为,通过*p来获得p所指对象的引用。

operator T*

operator T*() const throw()
{
return p;
}
这是个类型转换操作。调用方法如下:
CComPtr<IUnknown> sp=...
IUnknown* p=(IUnknown*)sp;

不能使用的operator&

//The assert on operator& usually indicates a bug. If this is really
//what is needed, however, take the address of the p member explicitly.
T** operator&() throw()
{
ATLASSERT(p==NULL);
return &p;
}

当你的CComPtr的p成员变量(集成而来的)不为NULL时,debug模式下会弹出错误对话框。看看上面的注释,这个函数被设计用来防止对智能指针直接调用取地址操作符的。实在要用,请用这种方法:&sp.p。

判断是否指向同一个COM对象的接口

bool operator==(_In_opt_ T* pT) const throw()
{
return p == pT;
}

bool operator!=(_In_opt_ T* pT) const
{
return !operator==(pT);
}

判断是否指向NULL

bool operator!() const throw()
{
return (p == NULL);
}
如果成员变量p为空,则返回true


常用方法和常见错误

由于ATL3.0以后版本的改进,常见的->Release方法已经不能通过编译了,我们再也不会犯这个错误。
CComPtr<T>对象可以作为参数进行值传递和引用传递,也可以做为返回值。放心使用吧。
下面方法是错误的:
IA *pA=NULL;
p->QueryInterfaces(...,&pA);//从某个已有接口查询获取IA
CComPtr<IA> spA(pA)

QueryInterface方法会导致pA->AddRef被调用一次,CComPtr<IA>构造函数又会调用一次AddRef,而最后spA析构时只调用了一次Release,因此COM对象没有被销毁,内存泄露了。正确做法如下:

IA *pA=NULL;
p->QueryInterfaces(...,&pA);
CComPtr<IA> spA;
spA.Attach(pA);
或者
CComPtr<IA> spA(pA)
p->QueryInterfaces(...,&pA.p);

总之,引用计数何时增加和减少心里要警惕,看看源代码还是非常必要的。

CComQIPtr类

继承自CComPtr<T>类,增加了一个模板参数IID,并且有默认的参数值。

template <class T, const IID* piid = &__uuidof(T)>
class CComQIPtr : public CComPtr<T>
{
public:
CComQIPtr() throw()
{
}
CComQIPtr(_In_opt_ T* lp) throw() :
CComPtr<T>(lp)
{
}
CComQIPtr(_In_ const CComQIPtr<T,piid>& lp) throw() :
CComPtr<T>(lp.p)
{
}
CComQIPtr(_In_opt_ IUnknown* lp) throw()
{
if (lp != NULL)
lp->QueryInterface(*piid, (void **)&p);
}
T* operator=(_In_opt_ T* lp) throw()
{
if(*this!=lp)
{
return static_cast<T*>(AtlComPtrAssign((IUnknown**)&p, lp));
}
return *this;
}
T* operator=(_In_ const CComQIPtr<T,piid>& lp) throw()
{
if(*this!=lp)
{
return static_cast<T*>(AtlComPtrAssign((IUnknown**)&p, lp.p));
}
return *this;
}
T* operator=(_In_opt_ IUnknown* lp) throw()
{
if(*this!=lp)
{
return static_cast<T*>(AtlComQIPtrAssign((IUnknown**)&p, lp, *piid));
}
return *this;
}
};
构造函数可以从IUnknown参数进行查询,找到想要的接口。

增加了一个拷贝赋值函数和一个从IUnknown接口查询获取接口的赋值函数。红色的函数和CComPtr<T>完全一样,不知道为什么要定义?

下面是如何使用的例子:
CComPtr<IUnknown> punk = /* Init to some IUnknown* */ ;
CComQIPtr<INamedObject> pno = punk; // Calls punk->QI // (IID_INamedObject, ...)

一般情况下,我们应该总是用更高级的CComQIPtr类。

分享到:
评论

相关推荐

    ATL智能指针类CComPtr与CComQIPtr解析.pdf

    ATL智能指针类CComPtr与CComQIPtr解析.pdf

    ATL智能指针类CComPtr与CComQIPtr解析

    ATL智能指针类CComPtr与CComQIPtr解析

    ATL常用包装类演示

    ATL提供了很多复杂数据类型的包装类,使用这些包装类可以大大减小开发工作量,演示类常见CComBSTR CComVariant CComPtr的使用方法和注意事项。

    深入解析ATL(第2版).pdf

    3.4 智能指针类CAutoPtr和CAutoVectorPtr 3.5 ATL内存管理器 3.6 总结 第4章 ATL中的对象 4.1 实现IUnknown 4.2 ATL的层次 4.3 线程模型支持 4.4 IUnknow核心 4.5 我们的类 4.6 CComObject以及其他 4.7 ATL创建者 ...

    ATL COM简单类

    ATL简单类。现在不知道怎么用。希望有高手能看看。告诉怎么调用。

    atl开发指南 atl开发指南 atl开发指南 atl开发指南 atl开发指南

    atl开发指南 atl开发指南 atl开发指南 atl开发指南 atl开发指南

    ATL实现的CDHtmlDialog模板类

    &lt;br&gt;基于这个原因,通过理解分析MFC中CDHtmlDialog类的功能和实现行为,这里完全使用ATL一样的实现机制来模仿MFC中实现的功能编写了一个头文件,使ATL爱好者在无需MFC庞大的支持库的情形下实现跟CDHtmlDialog...

    深入解析ATL(第2版) ATL internals 2nd Edition Working with ATL8

    深入分析ATL实现COM内幕细节,展示COM应用中的各类漂亮技巧 本书主要介绍了ATL技术的原理、内部实现和应用技巧,由当今4 位顶尖的 Windows技术专家联合撰写。全书内容丰富,深入浅出,主要涵盖了ATL内部架构和实现...

    ATL 技术内幕 ATL技术内幕

    ATL技术内幕ATL技术内幕ATL技术ATL技术内幕内幕

    ocx动态创建

    通过使用CreateControl方法动态图创建ocx控件,并利用GetControlUnknown等方法获取IDispatch指针, 采用 ATL 智能指针类调用 IDispatch 接口的方法和标准方式调用 IDispatch 接口的方法,使用activex控件,包括一个...

    ATL开发指南 vc Atl编程

    ATL开发指南.rar 完整的 ATL开发指南 提供下载 学习com atl编程非常好的资料

    ICESAT-2 +ATL03 + ATL08

    适合人群: 对森林、地形、冰川等地物高度研究的人群 如何将星载lidar ICESAT-2的atl8和atl3结合,综合利用地理定位信息与地面高度信息

    C++ATL服务类

    VC6.0可以自动创建服务,到了2005又到了2010就不可以了,VC6.0健的服务升级也有错,只能自己编写服务类实现。

    ATL简明教程 ( ATL.zip )

    ATL简明教程 1. 介绍ATL的使用.

    ATL 2013 ,纯静态版本的ATL

    ATL+WTL,Windows平台仍然是一对锋利的组合。 主要特点: http://blogs.msdn.com/b/vcblog/archive/2013/08/20/atl-and-mfc-changes-and-fixes-in-visual-studio-2013.aspx One of the major changes we made was ...

    ATL例子(ATL简单对象和ATL控件)

    VS2003下编译通过,包含两个ATL的例子,1)创建一个简单ATL对象,目的弹出一个Messagebox输出一句话,附加测试程序。程序中要注意COM的初始化。 2)创建一个ATL控件,嵌入到网页中,实现功能为,点击控件中三角形...

    C++中ATL与WTL学习

    第一部分 - ATL 中的 GUI 类 • 下载示例工程 - 45.5 KB 本章内容 • README.TXT • 本系列介绍 • 第一部分介绍 • ATL 背景知识 o ATL 和 WTL 的历史 o ATL 风格的模板 • ATL 窗口类 • 定义窗口实现 o 填充消息...

    从ATL摘出来的一个简单的注册表操作类

    从ATL摘出来的一个简单的注册表操作类 从ATL摘出来的一个简单的注册表操作类

    curve lines,atl集合类vb测试

    atl集合类vb测试

    ATL内部框架流程分析

    我花了一个多月研究的ATL框架流程分析,写下了这个文档,该文档非常宝贵,详细的解释了ATL的工作流程,将会令你大开眼界!

Global site tag (gtag.js) - Google Analytics