CComPtr && CComQIPtr

本文详细介绍了COM编程中使用的智能指针CComPtr和CComQIPtr,包括它们的作用、使用方法及注意事项。通过具体示例展示了这些智能指针如何帮助开发者避免内存管理和异常处理中的常见错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CComPtr && CComQIPtr

COM接口指针很危险,因为使用过程中需要每一个使用者都要严格并且正确的AddRef和Release,一旦出现问题,就会造成对象不能被正常释放,或者对象被重复删除,造成程序崩溃。所以使用COM接口,必须小心翼翼才行。
但是,即使所有的代码中,都正确的AddRef和Release,也不一定能保证万无一失,例如:
void SomeApp( IHello * pHello )
{
IHello* pCopy = pHello;
pCopy->AddRef(); 
OtherApp();
pCopy->Hello();
pCopy->Release();
}
看起来好像无懈可击,但是假设OtherApp中抛出了异常,那么pCopy->Release不就被跳过去了吗?
幸好,所有的问题都从简单到复杂,再从复杂到简单的,因为我们有CComPtr!

CComPtr被称为智能指针,是ATL提供的一个模版类,能够从语法上自动完成AddRef和Release。(源代码在atlbase.h中)
CComPtr的用法很简单,以IHello*为例,将程序中所有接口指针类型(除了参数),都使用CComPtr<IHello> 代替即可。即程序中除了参数之外,再也不要使用IHello*,全部以CComPtr<IHello>代替。
CComPtr的用法和普通COM指针几乎一样,另外使用中有以下几点需要注意。
1. CComPtr已经保证了AddRef和Release的正确调用,所以不需要,也不能够再调用AddRef和Release。
2. 如果要释放一个智能指针,直接给它赋NULL值即可。
3. CComPtr本身析构的时候会释放COM指针。
4. 当对CComPtr使用&运算符(取指针地址)的时候,要确保CComPtr为NUL。(因为通过CComPtr的地址对CComPtr赋值时,不会自动调用AddRef,若不为NULL,则前面的指针不能释放,CComPtr会使用assert报警)
以刚才的程序为例:
void SomeApp( IHello * pHello )
{
CComPtr<IHello> pCopy = pHello;
OtherApp();
pCopy->Hello();
}
由于pCopy是一个局部的对象,所以即使OtherApp()抛出异常,pCopy也会被析构,指针能够被释放。
如果不想在程序临近发布前,还因为COM指针的引用计数造成崩溃的话,就牢记这一点吧:程序中除了参数之外,不要直接使用COM指针类型,一定要全部以CComPtr<IXXX>代替。

CComPtr和CComQIPtr是智能接口指针类,它们在销毁的时候,不需要手动去释放接口指针,在赋值的时候,也不需要手动的AddRef,在出现异常的时候,会自动处理异常,而不需要额外的异常处理代码。

CComPtr和CComQIPtr的不同的地方:CComPtr只能创建固定的特定的接口指针实例。而CComQIPtr不但实现了CComPtr的所有的功能,而且当我们把一个不同类型的接口指针赋值给CComQIPtr的时候,CComQIPtr会自动的调用接口指针的QueryInterface接口,来获得对应的正确的接口指针。 

1) 构造函数

第一个参数为智能接口指针的类型,第二个参数为 智能指针的接口ID。


CComPtr<IUnknown> punk;


//下面三个例子完全相同

CComPtr<IXXX> pno;

CComPtr<IXXX,&__uuidof(IXXX)> pno;

CComPtr<IXXX,&IID_IXXX> pno;


CComQIPtr可以用任何一个类型的接口指针初始化,如果初始化的值与CComQIPtr的类型相同,那么构造函数简单调用 指针的AddRef,但是,如果类型不同的话,它会先调用指针的QueryInterface来获得相同的类型的接口指针,当QueryInterface失败的话,内部指针会被设置为NULL。

下面的代码,可以用来检测是否转换成功:

void Func(IUnknown * punk) 

CComQIPtr<IXXX> pno(punk); 
if(pno) 

//正确转换 
pno->doSomething(); 

(2)赋值

赋值的时候,发生下面三件事:

111. 如果当前指针不为空,那么释放当前指针

222. 如果源指针不为空,那么AddRef

333. 将当前指针设置为源指针
oprator = 可以让我们把任何一个CComPtr赋值给CComQIPtr对象,如果有必要就会调用QueryInterface .
例如: 
CComQIPtr<IFoo> fooPtr; 
CComQIPtr<IBar> barPtr; 
barPtr=fooPtr; 

(3) CoCreateInstance方法

CComPtr提供了一个实例化对象的方法
HRESULT CoCreateInstance(REFCLSID rclsid,LPUNKNOWN pUnkOuter=NULL, DWORD dwClsCOntext=CLSCTX_ALL); 

HRESULT CoCreateInstance(LPCOLESTR szProgID, LPUNKNOSWN

pUnkOuter=NULL, DWORD dwClsContxt=CLSCTX_ALL);


后两个参数不管,第一个参数要不传入CLSID,要不传入字符串形式的progID. 

例子代码如下: 
CComPtr<IXXX> ptr; 
HRESULT hr=ptr.CoCreateInstance(__uuidof(IXXX)); 

(4) operator *()

当对CComPtr解除指针的引用时,跟普通的指针一样,返回一个内部指针类型的引用。


(5) operator &()
获取智能指针对象的地址,实际上也是返回内部指针的地址,跟普通指针一样。


(6) 在调用ComUninitialize方法前,需要手动释放所有的全局或者静态变量。
如何手动释放呢:

111.设置指针指向NULL

222.调用Release方法,注意是要调用智能指针的Release方法,而不是内部指针的,指针指针的Release方法,会调用内部指针的Relase,然后设置内部指针为NULL,这样就可以防止多次释放接口。


(7)CopyTo方法,拷贝后的智能指针生命周期完全独立。
我们用CopyTo方法,将指针拷贝到一个out型参数。


(8)Detach 和Attach方法
在返回一个我们不再需要的接口指针给调用者的时候,我们可以用Deatch方法。
当我们需要把原始指针的所有权转移到智能指针时候,用Attach方法。

(9) QueryInterface方法

他只需要传入期望获得的接口类型的变量地址,即可。
CComPtr<IFoo> pFoo=...;

CComPtr<IBar> pBar;
HRESULT hr= pFoo.QueryInterface(&pBar);


(10) IsEqualObject方法

IsEqualObject方法用来判断两个接口指针释放引用的是同一个对象。 


(11) != 和 == 操作符
跟普通的 一样


(12) CComPtr对IDispatch的特化
CComPtr<IDispatch> iptr;
属性调用的辅助函数

111. GetIDOfName(LPCOLESTR lpsz,DISPID * pdispid) 

这个方法,获得属性的DISPID


222.GetProPerty(DISPID dwDispid, VARIANT * pVar)

这个方法,获得属性。

SetProPerty(DISPID dwDispid, VARIANT * pVar)

这个方法,设置属性。


333. GetPropertyByName(LPCOLESTR lpsz, VARIANT * pVar)

SetPropertyByName(LPCOLESTR lpsz, VARIANT * pVar)

直接通过名称,获得和设置属性。

方法调用的辅助函数:

111. HRESULT Invoke0(DISPID dispid,VARIANT * pvarRet=NULL);

通过DISPID调用 没有参数的方法。


HRESULT Invoke0(LPCOLESTR lpszName, VARIANT * pvarRet=NULL);

通过方法名称,调用没有参数的方法。


HRESULT Invoke1(DISPID dispid,VARIANT * param1, VARIANT *

pvarRet=NULL);

通过DISPID调用 有一个参数的方法。


HRESULT Invoke1(LPCLOESTR lpszName ,VARIANT * param1,

VARIANT * pvarRet=NULL);

通过方法名称,调用有一个参数的方法。

HRESULT InvokeN(DISPID dispid,VARIANT* params, int nParams, VARIANT * pvarRet=NULL);

通过DISPID调用有N个参数的方法。


HRESULT InvokeN(LPCLOESTR lpszName ,VARIANT * params,int nParams, VARIANT * pvarRet=NULL);

通过方法名称调用有N个参数的方法。


注意,通过参数列表的方法调用的时候,参数是反向的顺序,最后一个参数是元素0。 切记

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值