从 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://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。
?
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 使用中的一个误区的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OpenGL编程指南4:双缓冲实现运行
- 下一篇: VC++下的OpenGL编程