程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 有效的使用和設計COM智能指針—條款4:理解ATL的CComPtr提倡簡單

有效的使用和設計COM智能指針—條款4:理解ATL的CComPtr提倡簡單

編輯:關於C語言

條款4:理解ATL的CComPtr提倡簡單,高效
微軟推出COM SDK後很快就意識到直接使用SDK開發COM是一件很困難的事情。於是他所做的第一件事情是將COM集成到MFC中去。但是隨著Internet的發展,分布式組件要求COM能在網絡上傳輸,但這卻給MFC開發COM組件帶來了相當大的障礙——MFC臃腫、龐大而且還要依賴很多DLL文件。在這種情況下ATL誕生了。

ATL【2】是ActiveX Template Library 的縮寫。不同於MFC,ATL采用了如多繼承和模版這兩種C++的高階編程技巧。開發人人員不僅能夠快速地開發出高效、簡潔的代碼(Effective and Slim code),而且開發出的組件更加的輕便小巧。但由於模版和多繼承的加入,學習ATL也更加復雜一些。

而CComPtr是ATL為了解決COM引用計數帶來的問題,提供的一個類模版。因此你可能明白了。智能指針需要通過模版來實現(大多數智能指針考慮到通用性都是這麼設計,也有少數例外)。因此你似乎明白了“智能指針”只是這種特定模版的一個較為通俗易懂的別名。它不會讓人有種,因為使用了模版這類高級的C++編程技巧而產生高不可攀的感覺。同時在使用和它的行為上也更加類似於一個接口指針。

ATL除了提供CComPtr還提供了一個名為CComQiPtr的智能指針。兩個智能指針的模版類用於實現對COM接口引用計數的自動管理,且都在<atlbase.h>中聲明。

這兩個模版類都繼承自CComPtrBase,不同之處在於CComQiPtr能在必要的時候自動的對所需接口進行查詢(如:對與此智能指針參數化類型不同的指針賦值時,會自動查詢是否有所需的接口)。CComPtrBase類封裝了CComPtr和CComQiPtr中公共的大多數函數,從而實現代碼的復用。下圖顯示了這3個模版類的繼承關系:

  

值得注意的是如果你的VC編譯器版本過於老舊(比如vc6.0)則在ATL中無法使用到CComQiPtr,也看不到CComPtrBase這個基類。僅僅有CComPtr孤零零的呆在<atlbase.h>這個頭文件中。這是因為在早期的ATL中,設計者只是設計了CComPtr這麼一個模版類。而後期為了加入新的功能才將CComPtr和CComQiPtr的代碼抽出來放入到CComPtrBase這個基類中去。

因此本文僅討論CComPtr以及其基類CComPtrBase,而不涉及關於CComQiPtr的內容(有興趣的讀者可以查閱MSDN或閱讀ATL源碼了解其中的細節)。在CComPtr中除了構造函數和賦值運算符之外的大多數函數都源自於CComPtrBase,為了方便閱讀和理解,本文會將這兩個類中的成員函數放在一起討論。

讓我們再來看一次CComPtr的使用,或許進過一番介紹以後,你會對他內部如何管理引用計數有更加深刻的理解:

以IHello*為例,將程序中所有接口指針類型(除了參數),都使用CComPtr<IHello> 代替即可。即程序中除了參數之外,再也不要使用IHello*,全部以CComPtr<IHello>代替。

如下:

view plaincopy to clipboardprint?void SomeApp( IHello * pHello ) 

    CComPtr<IHello> pCopy = pHello; 
    OtherApp(); 
    pCopy->Hello(); 

void SomeApp( IHello * pHello )
{
    CComPtr<IHello> pCopy = pHello;
    OtherApp();
    pCopy->Hello();
}
 

最後值得一提的是,雖然CComPtr的用法和普通COM接口指針類似,但是還是要主要如下幾個問題:

1. CComPtr已經保證了AddRef和Release的正確調用,所以不需要,也不能夠再調用AddRef和Release。

2. 如果要釋放一個智能指針,直接給它賦NULL值即可。

3. CComPtr本身析構的時候會釋放COM指針。

4. 當對CComPtr使用&運算符(取指針地址)的時候,要確保CComPtr為NUL。(因為通過CComPtr的地址對CComPtr賦值時,不會自動調用AddRef,若不為NULL,則前面的指針不能釋放,CComPtr會使用assert報警)

ATL追求的是簡潔與高效,因此在解決一個問題之時,CComPtr不會將解決問題的方式做到面面俱到。例如如果用一種方法能夠解決這一問題,ATL盡可能不會采用第二種方法(除非這兩種方法確實有不同之處)。

這一點從ATL的構造函數可以看出,他只有4個構造函數:1個必要的默認構造函數,1個必要的拷貝構造函數。還有2個為了滿足指針語法而存在的轉換構造函數。這樣“恰當好處”就足夠了。至於組件的創建過程,接口的查詢過程,ATL認為這些東西都可以通過構造好之後的智能指針來完成。如下:

view plaincopy to clipboardprint?CComPtr<ICalculator> spCalculator= NULL; 
hrRetCode = spCalculator.CoCreateInstance(CLSID_CALCULATOR); 
KG_COM_ASSERT_EXIT(hrRetCode); 
hrRetCode = spCalculator->Add(1, 2, &nSum); 
KG_COM_ASSERT_EXIT(hrRetCode); 
CComPtr<ICalculator> spCalculator= NULL;
hrRetCode = spCalculator.CoCreateInstance(CLSID_CALCULATOR);
KG_COM_ASSERT_EXIT(hrRetCode);
hrRetCode = spCalculator->Add(1, 2, &nSum);
KG_COM_ASSERT_EXIT(hrRetCode);
 

上述代碼中你可能會懷疑智能指針沒有滿足RAII,但是他確實滿足了。RAII並沒有要求資源管理對象與資源同聲明周期。他只是需要資源在獲取之處就與一個資源管理對象綁定起來。ATL認為這樣做最夠了,而且代碼清晰簡單,那麼他這樣做了。

再來看看CComPtr的賦值運算符,他並沒有做自動的接口查詢工作(需要說明的是,CComPtr在後續版本中為部分賦值運算符加入了此種操作)。ATL認為有了QueryInterface這套接口,則開發人員就能方便的完成接口查詢工作了。如下:

view plaincopy to clipboardprint?ICalculator* pCalculator = NULL; 
CComPtr<IUnknown> spIUnknown   = NULL; 
hrRetCode = spIUnknown.CoCreateInstance(CLSID_CALCULATOR); 
KG_COM_ASSERT_EXIT(hrRetCode); 
hrRetCode = spIUnknown.QueryInterface(&pCalculator);//通過此函數查詢接口  
KG_COM_ASSERT_EXIT(hrRetCode); 
hrRetCode = pCalculator->Add(1, 2, &nSum); 
KG_COM_ASSERT_EXIT(hrRetCode); 
ICalculator* pCalculator = NULL;
CComPtr<IUnknown> spIUnknown   = NULL;
hrRetCode = spIUnknown.CoCreateInstance(CLSID_CALCULATOR);
KG_COM_ASSERT_EXIT(hrRetCode);
hrRetCode = spIUnknown.QueryInterface(&pCalculator);//通過此函數查詢接口
KG_COM_ASSERT_EXIT(hrRetCode);
hrRetCode = pCalculator->Add(1, 2, &nSum);
KG_COM_ASSERT_EXIT(hrRetCode);
 

嗯~問題解決了,很符合ATL的哲學:簡單!高效!如果想了解更多的關於賦值運算符重載中查詢接口的問題可以查看條款26中“自動查詢接口帶來方便同時也潛藏危機”的論述。

作者“liuchang5的專欄”

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved