从 VC7 的 CHtmlView 不能正常退出谈 CComPtr 使用中的一个误区
生活随笔
收集整理的這篇文章主要介紹了
从 VC7 的 CHtmlView 不能正常退出谈 CComPtr 使用中的一个误区
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
一、錯(cuò)誤再現(xiàn)
在?VC7 中新建一個(gè) MDI 的 MFC Application,命名為MyHtml, 選擇使用 CHtmlView。
建立兩個(gè) 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/));
}
編譯并運(yùn)行這個(gè)程序,在子窗口打開后將其關(guān)閉。你會(huì)發(fā)現(xiàn)瀏覽器控件還在運(yùn)行。 二、錯(cuò)誤分析 在 VC7 中,MFC 在很大程度上使用了 ATL,CHtmlView 也不例外,在 CHtmlView 中,訪問 COM 指針的代碼被修改為使用 ATL 的 CComPtr。CComPtr 是一個(gè)對(duì) COM 指針進(jìn)行包裝的 ATL 模版,它實(shí)現(xiàn)了引用時(shí)自動(dòng) AddRef 和退出時(shí)自動(dòng) Release 這些以前很煩瑣的操作。而由其發(fā)展出來的 CComQIPtr 則更將 QueryInterface 包裝成 "=" 運(yùn)算符,更加方便使用。對(duì)于這兩個(gè)模版的詳細(xì)介紹,不在本文的探討范圍,我只能假設(shè)您已經(jīng)基本了解并已經(jīng)用過這兩個(gè)模版。 我們?cè)賮砜纯?VC7?的 CHtmlView 對(duì) CComPtr 的使用方法。在函數(shù) 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);
}
}
}
在我所標(biāo)記的一行中我們看到這樣的代碼: CComPtr<IDispatch> spDisp = GetHtmlDocument(); 而 GetHtmlDocument 的實(shí)現(xiàn)是什么樣的呢?我們?cè)賮砜纯?#xff1a; LPDISPATCH CHtmlView::GetHtmlDocument() const
{
ASSERT(m_pBrowserApp != NULL); LPDISPATCH result;
m_pBrowserApp->get_Document(&result);
return result;
}
可以知道,GetHtmlDocument 返回的是 get_Document 所輸出的一個(gè)接口指針,而我們知道,對(duì)于 COM 指針的一個(gè)使用原則是輸出參數(shù)時(shí)進(jìn)行引用計(jì)數(shù),也就是說我們所獲得的這個(gè) result 在 get_Document 內(nèi)部已經(jīng)對(duì)其進(jìn)行了 AddRef 調(diào)用,函數(shù)的調(diào)用者在不再需要這個(gè)指針的時(shí)候必須自行對(duì)指針進(jìn)行 Release。 繼續(xù),我們?cè)倩仡^看 OnFilePrint 的代碼,在代碼中使用了 CComPtr 重載過的 "=" 運(yùn)算符將函數(shù)的返回指針賦值給 spDisp。我們已經(jīng)知道 CComPtr 在函數(shù)退出的時(shí)候會(huì)自動(dòng)對(duì)其所包裝的指針進(jìn)行 Release,一切看起來都是正常而且天體無縫的。 那么到底錯(cuò)在哪里呢?恰恰就錯(cuò)在了這個(gè) "=" 上面。 依照 COM 指針的引用時(shí)計(jì)數(shù)的原則,CComPtr 在實(shí)現(xiàn)的時(shí)候?qū)崿F(xiàn)了自動(dòng)化的引用計(jì)數(shù)。即在任何 "=" 操作的時(shí)候 AddRef,而在無效時(shí) Release。我們來看看 "=" 運(yùn)算符的具體實(shí)現(xiàn)代碼是什么樣的: ATLINLINE ATLAPI_(IUnknown*) AtlComPtrAssign(IUnknown** pp, IUnknown* lp)
{
if (lp != NULL)
lp->AddRef();
if (*pp)
(*pp)->Release();
*pp = lp;
return lp;
}
從這段代碼可以知道,CComPtr 在拿到指針后,并不是直接將其保存到自己的指針里面,而是先對(duì)拿到的指針進(jìn)行 AddRef,保證引用計(jì)數(shù),而后才執(zhí)行 *pp = lp。 這樣以來,我們將三部分代碼合并起來就成了這樣: void CHtmlView::OnFilePrint() { LPDISPATCH result; // 函數(shù) GetHtmlDocument
m_pBrowserApp->get_Document(&result); // 函數(shù) GetHtmlDocument
IDispatch* spDisp; result->AddRef(); // CComPtr 自動(dòng)完成 spDisp = result; // CComPtr 自動(dòng)完成 ....... spDisp->Release(); // CComPtr 自動(dòng)完成 } 能夠看出其中的問題嗎?對(duì)了,result 并沒有被釋放。問題出在函數(shù)輸出的并不是一個(gè)引用計(jì)數(shù)完整的 COM 指針,而 CComPtr 并不知道,從而導(dǎo)致了這個(gè)指針最終被丟失。而 COM 對(duì)象也因?yàn)橐糜?jì)數(shù)并沒有回歸為零而不敢清除自己,最終導(dǎo)致了 CHtmlView 不能正常退出。 三、修改 通過對(duì)上面代碼的分析,我們已經(jīng)清楚了解了 CHtmlView 錯(cuò)誤的原因,下面我們就來試圖對(duì) CHtmlView 進(jìn)行修正。 1.將 PROGRAM FILES\MICROSOFT VISUAL STUDIO .NET\Vc7\atlmfc\src\mfc 目錄中的 viewhtml.cpp 復(fù)制到你自己的項(xiàng)目目錄,并將其加入到自己的項(xiàng)目中。 2.打開 viewhtml.cpp, 尋找 GetHtmlDocument。 3.將所有的直接將 GetHtmlDocument 函數(shù)返回賦值給 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 進(jìn)行測(cè)試,你會(huì)發(fā)現(xiàn)一切都正常了。看起來麻煩一些,但是是正確的。 四、結(jié)論 通過上面分析糾錯(cuò),我們可以知道,CComPtr 并不是一把萬能鑰匙,而對(duì) COM 指針的使用也遠(yuǎn)沒有因?yàn)?ATL 的出現(xiàn)而變得通俗起來。如果具體到這個(gè)例子,我們可以得到一個(gè)結(jié)論: 任何時(shí)候不要將函數(shù)的返回指針賦值給一個(gè) 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://www.openeim.com/));
}
編譯并運(yùn)行這個(gè)程序,在子窗口打開后將其關(guān)閉。你會(huì)發(fā)現(xiàn)瀏覽器控件還在運(yùn)行。 二、錯(cuò)誤分析 在 VC7 中,MFC 在很大程度上使用了 ATL,CHtmlView 也不例外,在 CHtmlView 中,訪問 COM 指針的代碼被修改為使用 ATL 的 CComPtr。CComPtr 是一個(gè)對(duì) COM 指針進(jìn)行包裝的 ATL 模版,它實(shí)現(xiàn)了引用時(shí)自動(dòng) AddRef 和退出時(shí)自動(dòng) Release 這些以前很煩瑣的操作。而由其發(fā)展出來的 CComQIPtr 則更將 QueryInterface 包裝成 "=" 運(yùn)算符,更加方便使用。對(duì)于這兩個(gè)模版的詳細(xì)介紹,不在本文的探討范圍,我只能假設(shè)您已經(jīng)基本了解并已經(jīng)用過這兩個(gè)模版。 我們?cè)賮砜纯?VC7?的 CHtmlView 對(duì) CComPtr 的使用方法。在函數(shù) 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);
}
}
}
在我所標(biāo)記的一行中我們看到這樣的代碼: CComPtr<IDispatch> spDisp = GetHtmlDocument(); 而 GetHtmlDocument 的實(shí)現(xiàn)是什么樣的呢?我們?cè)賮砜纯?#xff1a; LPDISPATCH CHtmlView::GetHtmlDocument() const
{
ASSERT(m_pBrowserApp != NULL); LPDISPATCH result;
m_pBrowserApp->get_Document(&result);
return result;
}
可以知道,GetHtmlDocument 返回的是 get_Document 所輸出的一個(gè)接口指針,而我們知道,對(duì)于 COM 指針的一個(gè)使用原則是輸出參數(shù)時(shí)進(jìn)行引用計(jì)數(shù),也就是說我們所獲得的這個(gè) result 在 get_Document 內(nèi)部已經(jīng)對(duì)其進(jìn)行了 AddRef 調(diào)用,函數(shù)的調(diào)用者在不再需要這個(gè)指針的時(shí)候必須自行對(duì)指針進(jìn)行 Release。 繼續(xù),我們?cè)倩仡^看 OnFilePrint 的代碼,在代碼中使用了 CComPtr 重載過的 "=" 運(yùn)算符將函數(shù)的返回指針賦值給 spDisp。我們已經(jīng)知道 CComPtr 在函數(shù)退出的時(shí)候會(huì)自動(dòng)對(duì)其所包裝的指針進(jìn)行 Release,一切看起來都是正常而且天體無縫的。 那么到底錯(cuò)在哪里呢?恰恰就錯(cuò)在了這個(gè) "=" 上面。 依照 COM 指針的引用時(shí)計(jì)數(shù)的原則,CComPtr 在實(shí)現(xiàn)的時(shí)候?qū)崿F(xiàn)了自動(dòng)化的引用計(jì)數(shù)。即在任何 "=" 操作的時(shí)候 AddRef,而在無效時(shí) Release。我們來看看 "=" 運(yùn)算符的具體實(shí)現(xiàn)代碼是什么樣的: ATLINLINE ATLAPI_(IUnknown*) AtlComPtrAssign(IUnknown** pp, IUnknown* lp)
{
if (lp != NULL)
lp->AddRef();
if (*pp)
(*pp)->Release();
*pp = lp;
return lp;
}
從這段代碼可以知道,CComPtr 在拿到指針后,并不是直接將其保存到自己的指針里面,而是先對(duì)拿到的指針進(jìn)行 AddRef,保證引用計(jì)數(shù),而后才執(zhí)行 *pp = lp。 這樣以來,我們將三部分代碼合并起來就成了這樣: void CHtmlView::OnFilePrint() { LPDISPATCH result; // 函數(shù) GetHtmlDocument
m_pBrowserApp->get_Document(&result); // 函數(shù) GetHtmlDocument
IDispatch* spDisp; result->AddRef(); // CComPtr 自動(dòng)完成 spDisp = result; // CComPtr 自動(dòng)完成 ....... spDisp->Release(); // CComPtr 自動(dòng)完成 } 能夠看出其中的問題嗎?對(duì)了,result 并沒有被釋放。問題出在函數(shù)輸出的并不是一個(gè)引用計(jì)數(shù)完整的 COM 指針,而 CComPtr 并不知道,從而導(dǎo)致了這個(gè)指針最終被丟失。而 COM 對(duì)象也因?yàn)橐糜?jì)數(shù)并沒有回歸為零而不敢清除自己,最終導(dǎo)致了 CHtmlView 不能正常退出。 三、修改 通過對(duì)上面代碼的分析,我們已經(jīng)清楚了解了 CHtmlView 錯(cuò)誤的原因,下面我們就來試圖對(duì) CHtmlView 進(jìn)行修正。 1.將 PROGRAM FILES\MICROSOFT VISUAL STUDIO .NET\Vc7\atlmfc\src\mfc 目錄中的 viewhtml.cpp 復(fù)制到你自己的項(xiàng)目目錄,并將其加入到自己的項(xiàng)目中。 2.打開 viewhtml.cpp, 尋找 GetHtmlDocument。 3.將所有的直接將 GetHtmlDocument 函數(shù)返回賦值給 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 進(jìn)行測(cè)試,你會(huì)發(fā)現(xiàn)一切都正常了。看起來麻煩一些,但是是正確的。 四、結(jié)論 通過上面分析糾錯(cuò),我們可以知道,CComPtr 并不是一把萬能鑰匙,而對(duì) COM 指針的使用也遠(yuǎn)沒有因?yàn)?ATL 的出現(xiàn)而變得通俗起來。如果具體到這個(gè)例子,我們可以得到一個(gè)結(jié)論: 任何時(shí)候不要將函數(shù)的返回指針賦值給一個(gè) CComPtr。
總結(jié)
以上是生活随笔為你收集整理的从 VC7 的 CHtmlView 不能正常退出谈 CComPtr 使用中的一个误区的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenGL编程指南4:双缓冲实现运行
- 下一篇: OpenGL编程指南5:学习绘制不同风格