最近在玩 MSHTML 和 MSXML,被一堆 COM 接口搞的晕头转向。
以前也玩过一些,但基本都是照抄别人的代码,也没注意到 IDispatch * 和 IDispatchPtr 有啥区别,就模糊的认为可能是同一个东东的两种写法罢……
这次趁机仔细研究了一番,转载3篇 COM 智能指针的详细的介绍,以备不时之需
ATL智能指针类
ATL中包含的智能指针包括:CAutoPtr、CAutoPtrArray和CAutoPtrList, CComPtr和CComQIPtr
一、CAutoPtr、CAutoPtrArray和CAutoPtrList
CAutoPtr是普通智能指针类,CAutoPtrArray是智能指针数组,两者都在atlbase.h中声明;CAutoPtrList是智能指针列表,在atlcoll.h中声明。
二、CComPtr和CComQIPtr
CComPtr和CComQIPtr都是用来管理COM接口指针的类,CComQIPtr可完全取代CComPtr,两者的区别在于:CComQIPtr实现了运算符的重载功能,它可以自动调用QueryInterface()函数,CComQIPtr 唯一的缺点是不能定义 IUnknown * 指针。CComQIPtr<IUnknown > spUn是错误的。
1.声明和定义:
声明和定义一个CComQIPtr指针ISample。
CComQIPtr<ISample> spSample(IOtherInterface); 等价于
CComQIPtr<ISample> spSample =IOtherInterface;/*运算符重载*/ 等价于
CComQIPtr<ISample> spSample;
IOtherInterface->QueryInterface(IID_ISample, &spSample);
2.函数调用:
智能指针调用函数分智能指针函数调用和智能指针内部接口指针函数调用,如上面声明的spSample,
spSample.CoCreateInstance(),spSample.QueryInterface等价于API函数调用::CoCreateInstance()和::QueryInterface();
spSample.QueryInterface()等价于内部接口指针函数spSample->QueryInterface()。
spSample->AddRef()正确,但spSample->Release()是错误的,因为它调用的是内部接口函数,并不能完全释放智能指针,而后还会再调用一次Release()释放指针。
3.智能指针的使用
当对象的生命周期很长要小心使用;
当对象的生命周期不确定时不要使用;
千万不要使用全局的智能指针。
一个智能指针使用的问题:
MyFunc()
{
HRESULT hr;
CoInitialize(NULL);
{
CComPtr<ITestCOMPtr> spTestCOMPtr;
hr = spTestCOMPtr.CreateInstance(CLSID_TESTCOMPtr);
if(FAILED(hr))
{
returnl_hr;
}
//dosomethingof ITestCOMPtr
}
CoUninitialize();
}
如果没有红色括号标志智能指针的生命周期,spTestCOMPtr将在程序结束时,也就是CoUninitialize()结束之后才释放,这时套间已经结束,从而引起程序崩溃。
附: CComPtr 和 CComQIPtr类定义
template
class CComPtr {
public:
typedef T _PtrClass;
CComPtr() {p=NULL;}
CComPtr(T* lp) {
if ((p = lp) != NULL)
p->AddRef();
}
CComPtr(const CComPtr
& lp) {
if ((p = lp.p) != NULL)
p->AddRef();
}
~CComPtr() {if (p) p->Release();}
void Release() {if (p) p->Release(); p=NULL;}
operator T*() {return (T*)p;}
T& operator*() {_ASSERTE(p!=NULL); return *p; }
T** operator&() { _ASSERTE(p==NULL); return &p; }
T* operator->() { _ASSERTE(p!=NULL); return p; }
T* operator=(T* lp){return (T*)AtlComPtrAssign((IUnknown**)&p, lp);}
T* operator=(const CComPtr
& lp) {
return (T*)AtlComPtrAssign((IUnknown**)&p, lp.p);
}
bool operator!(){return (p == NULL);}
T* p;
};
template
class CComQIPtr
{
public:
typedef T _PtrClass;
CComQIPtr() {p=NULL;}
CComQIPtr(T* lp) {
if ((p = lp) != NULL)
p->AddRef();
}
CComQIPtr(const CComQIPtr& lp) {
if ((p = lp.p) != NULL)
p->AddRef();
}
CComQIPtr(IUnknown* lp) {
p=NULL;
if (lp != NULL)
lp->QueryInterface(*piid, (void **)&p);
}
~CComQIPtr() {if (p) p->Release();}
void Release() {if (p) p->Release(); p=NULL;}
operator T*() {return p;}
T& operator*() {_ASSERTE(p!=NULL); return *p; }
T** operator&() { _ASSERTE(p==NULL); return &p; }
T* operator->() {_ASSERTE(p!=NULL); return p; }
T* operator=(T* lp){return (T*)AtlComPtrAssign((IUnknown**)&p, lp);}
T* operator=(const CComQIPtr& lp) {
return (T*)AtlComPtrAssign((IUnknown**)&p, lp.p);
}
T* operator=(IUnknown* lp) {
return (T*)AtlComQIPtrAssign((IUnknown**)&p, lp, *piid);
}
bool operator!(){return (p == NULL);}
T* p;
};
//更新 修改上述代码,使之成为独立于ATL环境的纯c++ CComPtr类,主要是改写重载运算符函数operator=,实现如下:
T* operator=(T* lp){ASSERT(lp!=NULL);if(ISEqual(lp)) return p; p->release();lp->addref(); p=lp; return p;}检测是否为自赋值:BOOL ISEqual(IUnknown* pOther){ASSERT(lp); IUnknown pUnknown==NULL; p ->queryinterface(IID_IUnknown, (void**)&pUnknown);BOOL result=(pUnknown==pOther)?TRUE:FALSE; pUnknown->release; return result;}
T* operator=(const CComQIPtr& lp) {
ASSERT(&lp!=NULL);p=(T*)lp;if(p)p->addref();return p;}
T* operator=(IUnknown* lp) {
AASERT(lp); lp->queryinterface(IID_T,(void**)&p);ASSERT(p);return p);
}
_com_ptr_t的使用
对于COM调用,大量的AddRef/Release调用无疑是例行公事和让人生厌。
_com_ptr_t 是vc运行库中提供的固有的封装COM接口的智能指针,相比CComPtr/CComQIPtr缺少移植性,但是支持不是ATL实现部分的的异常和操作。
以下是DirectShow中播放一个文件的代码:
IGraphBuilder *pGraph;
IMediaControl *pMediaControl;
IMediaEvent *pEvent;
// Create the filter graph manager and query for interfaces.
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl);
pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
// Build the graph. IMPORTANT: Change string to a file on your system.
pGraph->RenderFile(L"C://Example.avi", NULL);
// Run the graph.
pMediaControl->Run();
// Wait for completion.
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);
// Clean up.
pMediaControl->Release();
pEvent->Release();
pGraph->Release();
使用com_ptr_t后简单了很多:
//这部分可以放到头文件中
#include
_COM_SMARTPTR_TYPEDEF( IGraphBuilder , __uuidof(IGraphBuilder) );
_COM_SMARTPTR_TYPEDEF( IMediaControl , __uuidof(IMediaControl) );
_COM_SMARTPTR_TYPEDEF( IMediaEvent , __uuidof(IMediaEvent) );
...
IGraphBuilderPtr pGraph( CLSID_FilterGraph ) ;//调用CreateInstance
IMediaControlPtr pMediaControl = pGraph;//这里会自动调用QueryInterface
IMediaEventPtr pEvent = pGraph;//这里会自动调用QueryInterface
pGraph->RenderFile( L"L"C://Example.avi", NULL);
// Run the graph.
pMediaControl->Run();
// Wait for completion.
long evCode;
pEvent->WaitForCompletion(INFINITE, &evCode);
//不需要调用Release()
假如我们要复制一个接口,常常要
pG1 = pG2;
pG1->AddRef();
...
pG1->Release();
对于_com_ptr_t , 只要pG1 = pG2;就可以了
注意以下的代码:
IGraphBuilderPtr pGraph ;
pGraph.CreateInstance( CLSID_FilterGraph );
...
CoUninitialize();
因为 pGraph 会在超出作用域时析构,也就是在CoUninitialize()之后调用 IUnkown::Release() ,这样会引起问题,可以这样解决:
pGraph.Release(); CoUninitialize();
或者
pGraph = NULL; CoUninitialize(); //这样会隐含的调用_com_ptr_t::Release()
绝对不可以调用pGraph->Release(); 因为这是调用IUnkown::Release()然后再调用一次pGraph.Release(); 将会两次释放接口指针。
从 VC7 的 CHtmlView 不能正常退出谈 CComPtr 使用中的一个误区
home.htm
<head>
<frameset rows="*,30">
<frame src="test.htm">
<frame src="about:blank">
</frameset>
</html>
test.htm
<html>
<head>
<script language="JavaScript"><!--
function FreshNew()
{
window.alert("I'am here.");
setTimeout('FreshNew();',2000);
}
setTimeout('FreshNew();',2000);
// --></script>
</head>
</html>
修改 CMyHtmlView 的 OnInitialUpdate()
void CMyHtmlView::OnInitialUpdate()
{
CHtmlView::OnInitialUpdate();
Navigate2(_T(" http://./home.htm"));
}
编译并运行这个程序,在子窗口打开后将其关闭。你会发现浏览器控件还在运行。
{
// get the HTMLDocument
{
CComPtr<IDispatch> spDisp = GetHtmlDocument();
{
// the control will handle all printing UI
if (spTarget != NULL)
spTarget->Exec(NULL, OLECMDID_PRINT, 0, NULL, NULL);
}
}
}
{
ASSERT(m_pBrowserApp != NULL);
m_pBrowserApp->get_Document(&result);
return result;
}
{
if (lp != NULL)
lp->AddRef();
if (*pp)
(*pp)->Release();
*pp = lp;
return lp;
}
m_pBrowserApp->get_Document(&result); // 函数 GetHtmlDocument
IDispatch* spDisp;
{
// get the HTMLDocument
{
CComPtr<IDispatch> spDisp;
{
// the control will handle all printing UI
if (spTarget != NULL)
spTarget->Exec(NULL, OLECMDID_PRINT, 0, NULL, NULL);
}
}
}