有效的使用和设计COM智能指针—条款5:了解_com_ptr_t 的设计背后
条款5:了解_com_ptr_t 设计背后的历史原因
_com_ptr_t是微软在VC中的一个专有模版类。它封装了对IUnknown的QueryInte易做图ce()、AddRef()和Release()的操作,并提供自己的一些成员函数从而对COM接口指针进行操作。同时_com_ptr_t还简化了COM接口对引用计数的操作以及不同接口间的查询操作。
要使用_com_ptr_t这个智能指针,首先需要用_COM_SMARTPTR_TYPEDEF这个宏来声明特异化(Specialization)版本的_com_ptr_t 类别。之后则可以使用形如“接口名称+Ptr”这样的名称来定义此种接口类型的智能指针。例如:
view plaincopy to clipboardprint?_COM_SMARTPTR_TYPEDEF(ICalculator, __uuidof(ICalculator));
_COM_SMARTPTR_TYPEDEF(ICOMDebugger,__uuidof(ICOMDebugger));
HRESULT Calculaltor()
{
ICOMDebuggerPtr spDebugger = NULL;
ICalculatorPtr spCalculator (CLSID_CALCULATOR); //构造函数可创建COM组件
int nSum = 0;
spCalculator->Add(1, 2, &nSum);
spDebugger = spCalculator; //自动调用QueryInte易做图ce查询所需要的接口
spDebugger->GetRefCount();
return S_OK;
}//无需手动调用Release(),接口会在智能指针析构时自动调用Release()。
_COM_SMARTPTR_TYPEDEF(ICalculator, __uuidof(ICalculator));
_COM_SMARTPTR_TYPEDEF(ICOMDebugger,__uuidof(ICOMDebugger));
HRESULT Calculaltor()
{
ICOMDebuggerPtr spDebugger = NULL;
ICalculatorPtr spCalculator (CLSID_CALCULATOR); //构造函数可创建COM组件
int nSum = 0;
spCalculator->Add(1, 2, &nSum);
spDebugger = spCalculator; //自动调用QueryInte易做图ce查询所需要的接口
spDebugger->GetRefCount();
return S_OK;
}//无需手动调用Release(),接口会在智能指针析构时自动调用Release()。
_COM_SMARTPTR_TYPEDEF这个宏,一般放置于单独的头文件中。这样,只要include了此头文件的相关文件,都能使用名称为“接口名+Ptr”这种类型的智能指针。
这使得_com_ptr_t这套智能指针使用起来相对比较简单,编写代码时不存在一大堆针对模版的类型参数化过程。使用者也感觉不到模版的存在,用类似接口指针的方式即可使用此智能指针。
如果想探究_com_ptr_t这套智能指针的特异化过程是如何完成的,我们可以将特异化时候所用到的_COM_SMARTPTR_TYPEDEF这个宏展开:
view plaincopy to clipboardprint?typedef _com_ptr_t<_com_IIID<IMyInte易做图ce, __uuidof(IMyInte易做图ce)>> IMyInte易做图cePtr;
typedef _com_ptr_t<_com_IIID<IMyInte易做图ce, __uuidof(IMyInte易做图ce)>> IMyInte易做图cePtr;
其中_com_IIID 的原型为:
view plaincopy to clipboardprint?template<typename _Inte易做图ce, const IID* _IID /*= &__uuidof(_Inte易做图ce)*/>
class _com_IIID
template<typename _Inte易做图ce, const IID* _IID /*= &__uuidof(_Inte易做图ce)*/>
class _com_IIID
可以看出_com_IID这个类模版的功能是对IID和具体的类型进行封装,并把他们绑定在一起。_com_ptr_t则再会将此_com_IID参数化之后的类型作为类型参数的实参,从而构造一个特异化版本的智能指针类型。
另外值得一提的是,如果希望使用__uuidof这个vc专用的关键字,则需要在接口声明的时候加上形如:
view plaincopy to clipboardprint?__declspec(uuid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"))
__declspec(uuid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"))
这样的语法。如下是ICalculator接口的声明:
view plaincopy to clipboardprint?inte易做图ce __declspec(uuid("994D80AC-A5B1-430a-A3E9-2533100B87CE")) ICalculator : IUnknown
{
virtual HRESULT STDMETHODCALLTYPE Add(
const int nNum1,
const int nNum2,
int *pnSum
) const = 0;
virtual HRESULT STDMETHODCALLTYPE Sub(
const int nMinuend,
const int nSubtrahend,
int *pnQuotient
) const = 0;
};
inte易做图ce __declspec(uuid("994D80AC-A5B1-430a-A3E9-2533100B87CE")) ICalculator : IUnknown
{
virtual HRESULT STDMETHODCALLTYPE Add(
const int nNum1,
const int nNum2,
int *pnSum
) const = 0;
virtual HRESULT STDMETHODCALLTYPE Sub(
const int nMinuend,
const int nSubtrahend,
int *pnQuotient
) const = 0;
};
在_com_ptr_t 中封装了更多的功能性函数(如可以在构造智能指针的时候创建COM组件),并可以通过赋值运算符进行接口的查询。或许你会问为什么CComPtr不提供类似的操作。这个议题涉及到智能指针设计原则上的问题。我们会在“在设计原则中斟酌取舍”进行深入的讨论。
看完_com_ptr_t的一些基础用法后,让我们再来设想一种情况:如果我们有一个COM组件,但却拿不到他的头文件,那么在VC中应该如何操作他们呢?或许你认为拿不到头文件却要调用函数的情况不太可能发生,因为这样做你的代码无法通过编译。但事实是,缺少C/C++头文件这一现象却存在于大量的COM组件之中。
这些COM的设计者并非没有照顾到C/C++的程序员(很大程度上,他们也使用C++开发COM),而是他们使用了一种更好的方法来声明组件的接口——类型库。
类型库,是一种与语言无关、适合于解释性语言和宏语言使用C++头文件的等价物【1】。换而言之,C++和C语言中,我们的类型声明都用头文件来代替,而VB、delphi,则可以通过类型库来完成。
微软为VC提供的#import预处理命令,它能将一个类型库转换成等价的C/C++头文件。这样,开发者只需要发布一套类型库,则能在多种语言中定义出相应的接口了。
我们先可以用#import预处理命令来导入一个类型库,看看编译器帮我们完成了什么。我们以ADO为例,用#import预处理命令导入ADO类型库的源代码像是下面这样的:
view plaincopy to clipboardprint?#import "C:\Program Files\Common Files\System\ado\msado15.dll" rename("EOF","rsEOF")
#import "C:\Program Files\Common Files\System\ado\msado15.dll" rename("EOF","rsEOF")
看上去有些复杂,而且和普通编译预处理命令形式上略有差别。但它却十分之方便,稍微编译一下这个程序,则会在相应的目录下输出msado15.tlh和msado15.tli两个文件。
msado15.tlh 包含了接口的声明,其内容看上去是下面这个样子的:
view plaincopy to clipboardprint?// Created by Microsoft (R) C/C++ Compiler Version 12.00.8168.0 (a2f27f36).
//
// d:\...\debug\msado15.tlh
//
// C++ source equivalent of Win32 type library C:\...\ado\msado15.dll
// compiler-generated file created 08/22/11 at 14:19:31 - DO NOT EDIT!
struct __declspec(uuid("00000512-0000-0010-8000-00aa006d2ea4"))
/* dual inte易做图ce */ _Collection;
struct __declspec(uuid("00000513-0000-0010-8000-00aa006d2ea4"))
/* dual inte易做图ce */ _DynaCollection;
struct __declspec(uuid("00000534-0000-0010-8000-00aa006d2ea4"))
/* dual inte易做图ce */ _ADO;
str
补充:软件开发 , C语言 ,