从 VC7 的 CHtmlView 不能正常退出谈 CComPtr 使用中的一个误区

 
从 VC7 的 CHtmlView 不能正常退出谈 CComPtr 使用中的一个误区
 
一、错误再现
 
在 VC7 中新建一个 MDI 的 MFC Application,命名为MyHtml, 选择使用 CHtmlView。
 
建立两个 html 文件:
 
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"));
}

编译并运行这个程序,在子窗口打开后将其关闭。你会发现浏览器控件还在运行。
 
二、错误分析
 
在 VC7 中,MFC 在很大程度上使用了 ATL,CHtmlView 也不例外,在 CHtmlView 中,访问 COM 指针的代码被修改为使用 ATL 的 CComPtr。CComPtr 是一个对 COM 指针进行包装的 ATL 模版,它实现了引用时自动 AddRef 和退出时自动 Release 这些以前很烦琐的操作。而由其发展出来的 CComQIPtr 则更将 QueryInterface 包装成 "=" 运算符,更加方便使用。对于这两个模版的详细介绍,不在本文的探讨范围,我只能假设您已经基本了解并已经用过这两个模版。
 
我们再来看看 VC7 的 CHtmlView 对 CComPtr 的使用方法。在函数 OnFilePrint 中,CHtmlView 的代码是这样的:
 
void CHtmlView::OnFilePrint()
{
  // get the HTMLDocument
  if (m_pBrowserApp != NULL)
 {
   CComPtr<IDispatch> spDisp = GetHtmlDocument();
   if (spDisp != NULL)
  {
   // the control will handle all printing UI
   CComQIPtr<IOleCommandTarget> spTarget = spDisp;
    if (spTarget != NULL)
    spTarget->Exec(NULL, OLECMDID_PRINT, 0, NULL, NULL);
  }
 }
}
在我所标记的一行中我们看到这样的代码:
CComPtr<IDispatch> spDisp = GetHtmlDocument();
 
而 GetHtmlDocument 的实现是什么样的呢?我们再来看看:
 
LPDISPATCH CHtmlView::GetHtmlDocument() const
{
 ASSERT(m_pBrowserApp != NULL);
 LPDISPATCH result;
  m_pBrowserApp->get_Document(&result);
  return result;
}
可以知道,GetHtmlDocument 返回的是 get_Document 所输出的一个接口指针,而我们知道,对于 COM 指针的一个使用原则是输出参数时进行引用计数,也就是说我们所获得的这个 result 在 get_Document 内部已经对其进行了 AddRef 调用,函数的调用者在不再需要这个指针的时候必须自行对指针进行 Release。
 
继续,我们再回头看 OnFilePrint 的代码,在代码中使用了 CComPtr 重载过的 "=" 运算符将函数的返回指针赋值给 spDisp。我们已经知道 CComPtr 在函数退出的时候会自动对其所包装的指针进行 Release,一切看起来都是正常而且天体无缝的。
 
那么到底错在哪里呢?恰恰就错在了这个 "=" 上面。
 
依照 COM 指针的引用时计数的原则,CComPtr 在实现的时候实现了自动化的引用计数。即在任何 "=" 操作的时候 AddRef,而在无效时 Release。我们来看看 "=" 运算符的具体实现代码是什么样的:
 
ATLINLINE ATLAPI_(IUnknown*) AtlComPtrAssign(IUnknown** pp, IUnknown* lp)
{
  if (lp != NULL)
   lp->AddRef();
  if (*pp)
  (*pp)->Release();
 *pp = lp;
  return lp;
}
从这段代码可以知道,CComPtr 在拿到指针后,并不是直接将其保存到自己的指针里面,而是先对拿到的指针进行 AddRef,保证引用计数,而后才执行 *pp = lp。
 
这样以来,我们将三部分代码合并起来就成了这样:
 
void CHtmlView::OnFilePrint()
{
 LPDISPATCH result; // 函数 GetHtmlDocument
 m_pBrowserApp->get_Document(&result); // 函数 GetHtmlDocument
 IDispatch* spDisp;
 
 result->AddRef(); // CComPtr 自动完成
 spDisp = result; // CComPtr 自动完成
 
 .......
 
 spDisp->Release(); // CComPtr 自动完成
}
 
能够看出其中的问题吗?对了,result 并没有被释放。问题出在函数输出的并不是一个引用计数完整的 COM 指针,而 CComPtr 并不知道,从而导致了这个指针最终被丢失。而 COM 对象也因为引用计数并没有回归为零而不敢清除自己,最终导致了 CHtmlView 不能正常退出。
 
三、修改
 
通过对上面代码的分析,我们已经清楚了解了 CHtmlView 错误的原因,下面我们就来试图对 CHtmlView 进行修正。
 
1.将 PROGRAM FILES/MICROSOFT VISUAL STUDIO .NET/Vc7/atlmfc/src/mfc 目录中的 viewhtml.cpp 复制到你自己的项目目录,并将其加入到自己的项目中。
2.打开 viewhtml.cpp, 寻找 GetHtmlDocument。
3.将所有的直接将 GetHtmlDocument 函数返回赋值给 CComPtr 指针的语句修改为使用 CComPtr 的 Attach。以 OnFilePrint 为例,代码将修改为下面的样子:
 
void CHtmlView::OnFilePrint()
{
  // get the HTMLDocument
  if (m_pBrowserApp != NULL)
 {
   CComPtr<IDispatch> spDisp;
 
  spDisp.Attach(GetHtmlDocument());
   if (spDisp != NULL)
  {
   // the control will handle all printing UI
   CComQIPtr<IOleCommandTarget> spTarget = spDisp;
    if (spTarget != NULL)
    spTarget->Exec(NULL, OLECMDID_PRINT, 0, NULL, NULL);
  }
 }
}
 
重新编译你的程序,再用最开始我提到的 html 进行测试,你会发现一切都正常了。看起来麻烦一些,但是是正确的。
 
四、结论
 
通过上面分析纠错,我们可以知道,CComPtr 并不是一把万能钥匙,而对 COM 指针的使用也远没有因为 ATL 的出现而变得通俗起来。如果具体到这个例子,我们可以得到一个结论:
 
任何时候不要将函数的返回指针赋值给一个 CComPtr。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值