一、错误再现
在 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://www.openeim.com/));
}

编译并运行这个程序,在子窗口打开后将其关闭。你会发现浏览器控件还在运行。

二、错误分析
在 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。

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

  1. 浅谈SQL注入风险 - 一个Login拿下Server(转)

    前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:"老师你这SQL有注 ...

  2. 互联网老炮儿谈用户,怎一个精字了得

    本文讲的是互联网老炮儿谈用户,怎一个"精"字了得[IT168评论]在写这篇文章之前,我把这位互联网老炮儿演讲的速记读了一遍又一遍,自己都不知道看了多少遍,总觉得他不是在演讲,而是在 ...

  3. 易语言程序应用程序错误退出_为什么我退出Google并构建了一个向孩子们教授个人理财的应用程序

    易语言程序应用程序错误退出 Many of my friends thought I was crazy to leave a great position at Google to help par ...

  4. linux c实现线程超时退出,c – 如何在另一个线程的超时内唤醒select()

    根据"男人选择"信息: "On success, select() and pselect() return the number of file descrip‐ to ...

  5. 老板面试怎么谈?千万不要走近误区

    关于人事面试.老板面试网上很多的文章.段子,特别是一些短视频的段子,标题党,总是把一些很极端的例子拿出来讲,教人们在面对老板怎么表现的与众不同,曾经我自己也非常相信那些观点,但当我接触了很多的面试,自 ...

  6. [转载]谈如何快速了解一个行业

    我觉得这是一个普遍性的问题.因此,在这里介绍一下我的思路.希望能给大家一些启发! 从一个门外汉,快速成为某个行业的专家,在我看来,有四种方式可以尝试: 第一种:就是前面介绍过的,找到这个行业内的企业, ...

  7. 小浩浅谈利用Java做一个视频运动追踪识别

    寒假期间,一起做了一款pc端的美颜相机,在交流会期间,看到有的同学做了一些非常牛的功能添加,心血来潮,想整个视频运动追踪识别,在这和大家分析一下算法思路 1.原理很简单,就是在视频中的物体,就可以通过 ...

  8. 浅谈HTTP事务的一个过程

    一个腾讯在职的朋友问道,当我们在浏览器的地址栏输入 www.baidu.com ,然后回车,这一瞬间页面发生了什么?下面以谷歌浏览器一一解释. 一.域名解析 首先Chrome浏览器会解析www.bai ...

  9. qt 一个线程接收数据 主线程更新界面 会造成界面退出 怎么解决_打造一个好产品...

    编辑导语:一个好的产品,关键在于产品经理和团队:产品经理对于产品如何理解以及产品更新迭代时的需求变化,产品如何实现更好的体验等等:本文作者分享了关于产品经理经常犯的七个问题,我们一起来看一下. 不管怎 ...

最新文章

  1. excel vlookup多个条件匹配多列_Excel中的Vlookup函数,轻松实现多条件查询!
  2. 三大数据驱动机制,助力文娱企业打造高留存、高转化增长闭环
  3. scp会覆盖同名文件吗_你会Hypermesh一键式完成几何文件到求解文件的输出吗?
  4. hdu 3062 Party(2-sat,3级)
  5. 【论文阅读】Deep Neural Networks for Learning Graph Representations | day14,15
  6. 12、SpringBoot------activeMq的简单使用
  7. 合并HTTP请求 vs 并行HTTP请求,到底谁更快?
  8. 记录08_7.15~7.16
  9. jszip在线解压压缩文件
  10. 软件开发角色知识概括
  11. [Poi 2012] bzoj2794 Cloakroom [dp]
  12. 揭示模式(Revealing Module)
  13. matlab曲线 投影,MATLAB地图工具箱学习心得(一)关于分带投影的拼接
  14. 第十九章 存储和使用流数据(BLOBs和CLOBs)
  15. android exoplayer 直播流,使用Exo-Media Player播放RTMP直播
  16. BC20 MQTT与GPS功能测试
  17. 决策树的预剪枝与后剪枝
  18. 22考研三战上交通信考研819电通初试第一428分经验分享
  19. 数据库SQL语句课堂总结(2)——子查询
  20. 太经典了,我不得不收藏!

热门文章

  1. 服务器重启导致无法启动MySQL
  2. 互联网晚报 | 11月21日 星期日 | B站公布《三体》动画首个预告片;涪陵榨菜回应天价礼盒;农行个人贷款余额突破7万亿元...
  3. Fastdata极数:2021年中国互联网基金投资用户报告
  4. 中国国民休闲状况调查(2020)
  5. 编写程序,删除数组中重复的元素,并统计各元素出现的次数
  6. 作者:谢华美(1976-),男,就职于中国人民银行征信中心数据部
  7. 作者:张悦今,女,中央财经大学信息学院讲师。
  8. 作者:林立,华中科技大学计算机科学与技术学院讲师。
  9. 大数据第1期——目录
  10. golang 复制对象的正确做法