1、简介
COM接口指针很危险,因为使用过程中需要每一个使用者都要严格并且正确的AddRef和Release,一旦出现问题,就会造成对象不能被正常释放,或者对象被重复删除,造成程序崩溃。所以使用COM接口,必须小心翼翼才行。但即使所有的代码中,都正确的AddRef和Release,也不一定能保证万无一失,在AddRef和Release中间的函数可能抛出异常,此时就没有执行到Release了。CComPtr智能指针就是解决这种问题而提出的。能够从语法上自动完成AddRef和Release。
ATL 提供了2个智能指针的模板包装类,CComPtr<> 和 CComQIPtr<>,这两个类都在 <atlbase.h> 中声明。CComQIPtr<> 包含了 CComPtr<>的所有功能,因此我们可以完全用 CComQIPtr<> 来使用智能接口指针,唯一要说明的一点就是:CComQIPtr<> 由于使用了运算符的重载功能,它会自动帮我们调用QueryInterface()函数,因此CComQIPtr<> 唯一的缺点就是不能定义 IUnknown * 指针。
命名按照匈牙利命名法,一般以sp开头表示变量类型,如:
CComPtr <接口类型> spTest; //正确
CComQIPtr< IUnknown > spUnk; // 错误!CComQIPtr不能定义IUnknown指针
2、给智能指针赋值
2.1 初始化
1 CComQIPtr< IFun > spFun; // 调用构造函数,还没有赋值,被包装的内部接口指针为 NULL 2 CComQIPtr< IFun > spFun( pOtherInterface ); // 调用构造函数,内部接口指针赋值 3 // 通过 pOtherInterface 这个普通接口指针调用QueryInterface()得到的IFun接口指针 4 5 CComQIPtr< IFun > spFun( spOtherInterface ); // 调用构造函数,内部接口指针赋值为 6 // 通过 spOtherInterface 这个智能接口指针调用QueryInterface()得到的IFun接口指针
赋值时发生3件事:
如果当前指针不为空,那么释放当前指针;如果源指针不为空,那么AddRef;将当前指针设置为源指针
2.2 智能指针赋值后,判断是否合法
1 if ( spFun ){} // 如果指针有效 2 if ( NULL != spFun ){} // 如果指针有效 3 4 if ( !spFun ){} // 如果指针无效 5 if ( NULL == spFun ){} // 如果指针无效 6
2.3 智能指针调用函数方法
spFun.CoCreateInstance(...); // 等价于 API 函数::CoCreateInstance(...)、
CComPtr<IXXX> ptr;
HRESULT hr=ptr.CoCreateInstance(__uuidof(IXXX));
spFun.QueryInterface(...); // 等价于 API 函数::QueryInterface()
spFun->Add(...); // 调用内部接口指针的接口函数
// 调用内部接口指针的QueryInterface()函数,其实效果和 spFun.QueryInterface(...) 一样
spFun->QueryInterface(...);
spFun.Release(); // 释放内部的接口指针,同时内部指针赋值为 NULL
spFun->Release(); // 错!一定不要这么使用。
// 因为这个调用并不把内部指针清空,那么析构的时候会被再次释放(释放了两次)
2.4 使用智能指针
好处:不需要显式的释放接口指针
例子:
首先要加载atlbase.h文件
1 #include “atlbase.h” 2 #include “xxx.h” 3 #include “xxx_i.c” 4 5 CComPtr<IUnknown> pIUnknown; // 定义 IUnknown 的智能指针 6 CComPtr<IObj1> pObj1; // 定义 IObj1 的智能指针 7 8 HRESULT hr; 9 try 10 { 11 //可以用CLSID,也可以用PROGID启动组件 12 hr = pIUnknown.CoCreateInstance(CLSID_Obj1); 13 if(FAILED(hr)) 14 { 15 throw(_T("启动组件出错")); 16 } 17 18 hr = pIUnknown.QueryInterface(&pObj1); 19 if(FAILED(hr)) 20 { 21 throw(_T("Query接口错误")); 22 } 23 24 long pVal; 25 hr = pObj1->add(147,258,&pVal); 26 if(FALSE(hr)) 27 { 28 throw(_T("加载函数出错")); 29 } 30 31 CString sMsg; 32 sMsg.Format( _T("147 + 258 = %ld"), pVal ); 33 AfxMessageBox( sMsg ); 34 } 35 36 catch (LPCTSTR lpstr) 37 { 38 AfxMessageBox(lpstr); 39 }
2.5 对象实例化方法
智能接口指针类提供了一个被称为CoCreateInstance的重载方法,用它可以是实例化一个对象,并且获得对象的一个接口指针.提供2种形式:
第1种要求:实例化类的类表识CLSID
1 HRESULT CoCreateInstance( 2 REFCLSID rclsid, 3 LPUNKNOWN pUnkOuter = NULL, 4 DWORD dwClsContext = CLSCTX_ALL 5 ) throw( );
第2种要求:实例化类的程序标识符ProgID
1 HRESULT CoCreateInstance( 2 LPCOLESTR szProgID, 3 LPUNKNOWN pUnkOuter = NULL, 4 DWORD dwClsContext = CLSCTX_ALL 5 ) throw( );
实例:
1 ISpeaker* pSpeaker; 2 3 HRESULT hr = ::CreateInstance(__uuidof(Demagogue),NULL,CLSCTX,__uuidof(ISpeaker,(void**)&pSpeaker); 4 5 pSpeaker->Release(); 6 7 8 CComPtr<ISpeaker> pSpeaker; 9 HRESULT hr = pSpeaker.CoCreateInstance(__uuidof(DEMAGOGUE));
3、CComQIPtr 和 CComPtr 的区别
template<class T,const IID* piid = &__uuidof(T)>
class CComQIPtr: public CComPtr<T>
除了构造函数以外,这两个模板提供的功能一模一样。
一般来说,CComQIPtr提供了几乎所有CComPtr的功能,但是有唯一一个例外,CComQIPtr<IUnknown>是不合法的,
template<class T, const IID* piid = &__uuidof(T)>class CComQIPtr 的构造符重复定义。
必须写成CComQIPtr<IUnknown, &IID_IUnknown>。
(#add 相比于 CComQIPtr< IUnknown > spUnk; // 错误!CComQIPtr不能定义IUnknown指针 )
除此之外,其他CComPtr都可以用CComQIPtr代替。
4、注意点
1)CComPtr已经保证了AddRef和Release的正确调用,所以不需要,也不能够再调用AddRef和Release。
2)如果要释放一个智能指针,直接给它赋NULL值即可。
3)CComPtr本身析构的时候会释放COM指针。
4)当对CComPtr使用&运算符(取指针地址)的时候,要确保CComPtr为NUL。
(因为通过CComPtr的地址对CComPtr赋值时,不会自动调用AddRef,若不为NULL,则前面的指针不能释放,CComPtr会使用assert报警)
5)牢记:若不希望因COM指针的引用计数造成崩溃,程序中除了参数之外,不要直接使用COM指针类型,一定要全部以CComPtr<IXXX>代替。
5、CComPtr和CComQIPtr 的资源管理
在调用ComUninitialize方法前,需要手动释放所有的全局或者静态变量。因为全局和静态变量的析构函数在主函数推出之后才执行。
但是此时CoUninitialize早已完成。
5.1 手动释放的方法
1)设置指针指向NULL
2)调用Release方法
注意是要调用智能指针的Release方法,而不是内部指针的Release方法,会调用内部指针的Relase,然后设置内部指针为NULL,这样就可以防止多次释放接口。即把智能指针赋值为NULL来释放内部的接口指针。
6、方法
CoCreateInstance方法:
CComPtr提供了一个实例化对象的方法,只需要传入第一个参数即可
operator *():
当对CComPtr解除指针的引用时,跟普通的指针一样,返回一个内部指针类型的引用
operator &():
获取智能指针对象的地址,实际上也是返回内部指针的地址,跟普通指针一样
CopyTo方法:
拷贝后的智能指针生命周期完全独立。将指针拷贝到一个out型参数
Detach 和Attach方法:
在返回一个我们不再需要的接口指针给调用者的时候,我们可以用Deatch方法。当我们需要把原始指针的所有权转移到智能指针时候,用Attach方法。
QueryInterface方法:
只需要传入期望获得的接口类型的变量地址
IsEqualObject方法:
IsEqualObject方法用来判断两个接口指针释放引用的是同一个对象
!= 和 == 操作符:
6.1 CComPtr对IDispatch的特化
CComPtr<IDispatch> iptr;
属性调用的辅助函数:
GetIDOfName(LPCOLESTR lpsz,DISPID * pdispid)获得属性的DISPID
GetProPerty(DISPID dwDispid, VARIANT * pVar)获得属性
SetProPerty(DISPID dwDispid, VARIANT * pVar)设置属性
GetPropertyByName(LPCOLESTR lpsz, VARIANT * pVar)直接通过名称,获得和设置属性
SetPropertyByName(LPCOLESTR lpsz, VARIANT * pVar)
方法调用的辅助函数:
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。