人小鬼不大

导航

 

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接口指针
View Code

 

赋值时发生3件事:
  如果当前指针不为空,那么释放当前指针;如果源指针不为空,那么AddRef;将当前指针设置为源指针

2.2 智能指针赋值后,判断是否合法

1       if ( spFun ){} // 如果指针有效
2   if ( NULL != spFun ){} // 如果指针有效
3    
4   if ( !spFun ){} // 如果指针无效
5   if ( NULL == spFun ){} // 如果指针无效
6        
View Code

 

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 }
View Code

2.5 对象实例化方法

  智能接口指针类提供了一个被称为CoCreateInstance的重载方法,用它可以是实例化一个对象,并且获得对象的一个接口指针.提供2种形式:
第1种要求:实例化类的类表识CLSID

1 HRESULT CoCreateInstance(
2   REFCLSID rclsid,
3   LPUNKNOWN pUnkOuter = NULL,
4   DWORD dwClsContext = CLSCTX_ALL
5 ) throw( );
View Code

第2种要求:实例化类的程序标识符ProgID

1 HRESULT CoCreateInstance(
2   LPCOLESTR szProgID,
3   LPUNKNOWN pUnkOuter = NULL,
4   DWORD dwClsContext = CLSCTX_ALL
5 ) throw( );
View Code

实例:

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));
View Code

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。

posted on 2020-01-20 15:19  人小鬼不大  阅读(1446)  评论(0编辑  收藏  举报