erface
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk[2]);
if (SUCCEEDED(hr)) {
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
rgpUnk[2]->Release();
}
rgpUnk[1]->Release();
}
rgpUnk[0]->Release();
}
}
void f(void) {
IUnknown *rgpUnk[3];
HRESULT hr = GetObject(&rgpUnk[0]);
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk [1]); //为了使得代码简单这里用GetObject代替QueryInterface
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk[2]);
if (SUCCEEDED(hr)) {
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
rgpUnk[2]->Release();
}
rgpUnk[1]->Release();
}
rgpUnk[0]->Release();
}
}
我并不觉得你能一眼看清楚上面代码的关键所在。只有当你一行一行读下来,你才会恍然大雾“原来只是为了调用UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2])这个函数”。而他需要的是三个COM接口指针。于是出现了这种层层嵌套的代码,以及嵌套之后的Release调用。你或许会用大量的精力来考虑括号是否配对,Release和GetObject是否成对出现。或许在它还使得你不得不拖动IDE下方或者右侧的滚动条来查看后续代码。
他不仅让人眼花,更重要的是他找不到关键逻辑代码。智能指针能简化这个编写过程,而且十分优雅:
view plaincopy to clipboardprint void f(void) {
CComPtr rgpUnk[3];
if (FAILED(GetObject(&rgpUnk[0]))) return;
if (FAILED(GetObject(&rgpUnk[1]))) return;
if (FAILED(GetObject(&rgpUnk[2]))) return;
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
}
void f(void) {
CComPtr rgpUnk[3];
if (FAILED(GetObject(&rgpUnk[0]))) return;
if (FAILED(GetObject(&rgpUnk[1]))) return;
if (FAILED(GetObject(&rgpUnk[2]))) return;
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
}
少了多余的AddRef()和Release(),世界清静了,你看到了UserObjects这个关键的逻辑,
或许你现在已经对智能指针跃跃欲试,因为它可以获取如此多的好处,而代价却相当之少(它只需要在函数堆栈上开辟一个极小的空间用于存放智能指针对象,大小往往也和普通指针的大小相同)。但在此之前,我们来看一些更加令人兴奋的特性。
观察下面代码:
view plaincopy to clipboardprint HRESULT hrRetCode = E_FAIL;
IX *pIX = NULL;
hrRetCode = CoCreateInstance(
CLSID_MYCOMPONENT,
NULL,
CLSCTX_INPROC_SERVER,
IID_IY, //哦~ 真悲剧,传错了IID。
(void **)&pIX
);
KG_COM_ASSERT_EXIT(hrRetCode);
pIX->fun();
HRESULT hrRetCode = E_FAIL;
IX *pIX = NULL;
hrRetCode = CoCreateInstance(
CLSID_MYCOMPONENT,
NULL,
CLSCTX_INPROC_SERVER,
IID_IY, //哦~ 真悲剧,传错了IID。
(void **)&pIX
);
KG_COM_ASSERT_EXIT(hrRetCode);
pIX->fun();
如果你仔细查看便会发现,查询接口的时候用IID_IY却用IX类型的指针作为参数接收。类似的错误还有可能是你查询的是IX但是用的是IY的接口进行接收。对于这样的错误代码,执行之后会发生什么,这实在没有什么值得我们深入研究的必要。而我们考虑得更多的应该是研究避免这一问题的方法。
首先来探究一下上述错误原因的关键:
1.IID 和接口类型没有静态的绑定在一起,这可能导致IID和接口的错误搭配。
2.CoCreateInstance的传出参数(最后一个参数)是void**类型,因此他是类型不安全的,完全有可能将任意类型的接口错误传入。
解决问题的办法仍然是智能指针。看一下下面这个优雅的方案,类型安全的问题似乎可以得到解决。
view plaincopy to clipboardprint HRESULT hrRetCode = E_FAIL;
CComPtr spIX
hrRetCode = spIX.CoCreateInstance(CLSID_MYCOMPONENT);//不存在IID和void**了
KG_COM_ASSERT_EXIT(hrRetCode);
pIX->fun();
HRESULT hrRetCode = E_FAIL;
CComPtr spIX
hrRetCode = spIX.CoCreateInstance(CLSID_MYCOMPONENT);//不存在IID和void**了
KG_COM_ASSERT_EXIT(hrRetCode);
pIX->fun();
以上代码中在智能指针后加“.”的用法貌似会让你对“指针”这个概念产生疑惑。你可能会问它不应该是->操作符吗?我们会在后面章节的讨论中涉及这个问题。暂且你不妨将智能指针理解为一个资源管理对象,而这个对象填充了一个安全创建COM组件的方法。类似的操作还存在智能指针对于QueryInterface这类操作中。
有些智能指针提供给我们一种更为方便的方式来创建COM组件和查询接口。如_com_ptr_t可以如下这种方式创建COM组件:
view plaincopy to clipboardprint _COM_SMARTPTR_TYPEDEF(ICalculator, __uuidof(ICalculator));
ICalculatorPtr spIX(CLSID_MYCOMPONENT);
KG_ASSERT_EXIT(spIX);
spIX->fun();
_COM_