1、12.1.7 使用智能指针创建 COM 对象1问题阐述利用 COM 的 API 创建 COM 对象,一切的处理工作都要程序员手动完成,比如接口指针最后的释放,这样如果在任务繁重的情况下,很容易出现忘记释放指针的情况,为了解决这个问题,COM 能够提供一种自动释放的机制,于是引入了智能指针。2实现技巧使用 COM 中的智能指针,使对象的创建工作更加简单化。而且它提供自动销毁生成的 COM 的对象机制,使程序的精力转移到其他的方面。下面看一下 ATL 提供的两个智能指针CcomPtr 和CComQIPtr。CcomPtr 类实现客户端基本的 COM 引用计数模型,CComPtr 有一个数据成员,它
2、是一个未经过任何加工的 COM 接口指针。其类型被作为模板参数传递。默认的构造函数将这个原始指针数据成员初始化为 NULL。智能指针的参数要么是原始指针,要么是相同类型的智能参数。不论哪种情况,智能指针都调用AddRef 控制引用。 CComPtr 的赋值操作符既可以处理原始指针,也可以处理智能指针,并且在调用新分配指针的 AddRef 之前自动释放保存的指针。最重要的是,CComPtr 的析构函数释放保存的接口(如果非空)。CComQIPtr 对于 CComPtr 只增加了两个成员函数,CComQIPtr 有两个模板参数:一个是被操纵的指针类型,另一个是对应于这个指针类型的 GUID。例如,
3、下列代码声明了操纵 IDataObject 和 IPersist 接口的智能指针:CComQIPtr spUnk;CComCComQIPtr 的优点是它有重载的构造函数和赋值操作符。同类版本(例如,接收相同类型的接口)仅仅进行 AddRef 右边的赋值/初始化操作,这实际上就是 CComPtr 的功能。异类版本(接收类型不一致的接口)正确调用 QueryInterface 来决定是否这个对象确实支持所请求的接口:void f(IFun* spUnk) CComQIPtr p;/ 同类赋值 - AddRefsp = spUnk;CComQIPtr do;/ 异类赋值 - QueryInterfa
4、cesdo = spUnk;CComPtr spUnk;CComPtr spFun;3实例代码本实例的目的借助于智能指针创建 COM 对象,建立一个基于对话框的工程。首先初始化应用工程的COM 库,在 CXXXXApp 的 InitInstance()中添加初始化语句:if(AfxOleInit()=FALSE)AfxMessageBox(“初始化环境 COM 库失败!“);return FALSE;引入智能指针类,引入组件的 CLSID、接口的 ID 及接口函数集:#include “ObjectObject.h“#include “ObjectObject_i.c“#include 智能指
5、针操作代码如下:void CExample2Dlg:OnExeBtn() UpdateData(TRUE);CComPtr spUnk; /定义 IUnknown 的智能指针CComPtr spFun; /定义 IFun 的智能指针tryHRESULT hr = spUnk.CoCreateInstance(CLSID_Fun,NULL,CLSCTX_INPROC_SERVER); /启动组件if(FAILED(hr)MessageBox(“组件没有注册!“);return ;hr = spUnk.QueryInterface( /查找 IFun 的接口if(FAILED(hr)Message
6、Box(“没有接口 IFun“);return;spFun-Add(m_add1,m_add2,CComBSTR s1(m_str1);CComBSTR s2(m_str2);CComBSTR s3;spFun-CatString(s1,s2,m_str3 = convert(s3.m_str); /将 BSTR 转换为 CString 同上catch(LPCTSTR str)MessageBox(str);UpdateData(FALSE); 上面的代码演示了使用 CcomPtr 智能指针,下面的代码演示了 CComQIPtr 的用法:void CExample2Dlg:OnComqiBtn
7、() UpdateData(TRUE);CComPtr spUnk; /定义 IUnknown 的智能指针CComQIPtr spFun; /定义 IFun 的智能指针tryHRESULT hr = spUnk.CoCreateInstance(CLSID_Fun,NULL,CLSCTX_INPROC_SERVER); /启动组件if(FAILED(hr)MessageBox(“组件没有注册!“);return ;spFun = spUnk; /会自动调用 QueryInterface 查找接口if(spFun = NULL)MessageBox(“没有接口!“);return;spFun-A
8、dd(m_add1,m_add2,CComBSTR s1(m_str1);CComBSTR s2(m_str2);CComBSTR s3;spFun-CatString(s1,s2,m_str3 = convert(s3.m_str); catch(LPCTSTR str)MessageBox(str);UpdateData(FALSE); ATL 提供了 2 个智能指针的模板包装类,CComPtr,这两个类都在 中声明。CComQIPtr的所有功能,因此我们可以完全用 CComQIPtr 由于使用了运算符的重载功能,它会自动帮我们调用QueryInterface()函数,因此 CComQIP
9、trQueryInterface( IID_IFun, / 也可以通过 QueryInterface 赋值/ 智能指针赋值后,可以用条件语句判断是否合法有效if ( spFun ) / 如果指针有效if ( NULL != spFun ) / 如果指针有效if ( !spFun ) / 如果指针无效if ( NULL = spFun ) / 如果指针无效智能指针调用函数的方法:spFun.CoCreateInstance(.); / 等价与 API 函数:CoCreateInstance(.)spFun.QueryInterface(.); / 等价与 API 函数:QueryInterfac
10、e()spFun-Add(.); / 调用内部接口指针的接口函数/ 调用内部接口指针的 QueryInterface()函数,其实效果和 spFun.QueryInterface(.) 一样spFun-QueryInterface(.); spFun.Release(); / 释放内部的接口指针,同时内部指针赋值为 NULLspFun-Release(); / 错!一定不要这么使用。/ 因为这个调用并不把内部指针清空,那么析构的时候会被再次释放(释放了两次)2 使用智能指针使用智能指针的好处是不需要我们去显示的释放接口指针首先要加载 atlbase.h 文件i nclude “atlbase.
11、h”i nclude “xxx.h”i nclude “xxx_i.c”CComPtr pIUnknown; / 定义 IUnknown 的智能指针CComPtr pObj1; / 定义 IObj1 的智能指针HRESULT hr;try/可以用 CLSID,也可以用 PROGID 启动组件hr = pIUnknown.CoCreateInstance(CLSID_Obj1);if(FAILED(hr)throw(_T(“启动组件出错“);hr = pIUnknown.QueryInterface(if(FAILED(hr)throw(_T(“Query 接口错误“);long pVal;hr
12、 = pObj1-add(147,258,if(FALSE(hr)throw(_T(“加载函数出错“);CString sMsg;sMsg.Format( _T(“147 + 258 = %ld“), pVal );AfxMessageBox( sMsg );catch (LPCTSTR lpstr)AfxMessageBox(lpstr);3 CComPtr 的代码template class CComPtrpublic:typedef T _PtrClass;CComPtr()p=NULL;CComPtr(T* lp)if (p = lp) != NULL)p-AddRef();CComP
13、tr(const CComPtrCComPtr()if (p)p-Release();void Release()IUnknown* pTemp = p;if (pTemp)AddRef(); OtherApp();pCopy-Hello();pCopy-Release();看起来好像无懈可击,但是假设 OtherApp 中抛出了异常,那么 pCopy-Release 不就被跳过去了吗?幸好,所有的问题都从简单到复杂,再从复杂到简单的,因为我们有 CComPtr!CComPtr 被称为智能指针,是 ATL 提供的一个模版类,能够从语法上自动完成 AddRef 和 Release。(源代码在 a
14、tlbase.h 中)CComPtr 的用法很简单,以 IHello*为例,将程序中所有接口指针类型(除了参数),都使用CComPtr 代替即可。即程序中除了参数之外,再也不要使用 IHello*,全部以 CComPtr代替。CComPtr 的用法和普通 COM 指针几乎一样,另外使用中有以下几点需要注意。1. CComPtr 已经保证了 AddRef 和 Release 的正确调用,所以不需要,也不能够再调用 AddRef 和Release。2. 如果要释放一个智能指针,直接给它赋 NULL 值即可。3. CComPtr 本身析构的时候会释放 COM 指针。4. 当对 CComPtr 使用OtherApp();pCopy-Hello();由于 pCopy 是一个局部的对象,所以即使 OtherApp()抛出异常,pCopy 也会被析构,指针能够被释放。如果不想在程序临近发布前,还因为 COM 指针的引用计数造成崩溃的话,就牢记这一点吧:程序中除了参数之外,不要直接使用 COM 指针类型,一定要全部以 CComPtr代替。#include