COM入門(mén)第一部分 - 什么是COM和如何使用COM
作者:Michael Dunn
譯者:蔣國綱
本文目的
此文為剛開(kāi)始學(xué)習COM并需要一些幫助來(lái)認識其基礎的程序員而寫(xiě),文章簡(jiǎn)要地覆蓋了COM的規范,解釋一些COM的術(shù)語(yǔ)和怎樣重復使用存在的COM組件,但本文并不覆蓋創(chuàng )建一個(gè)COM的內容。(譯者:關(guān)于如何創(chuàng )建一個(gè)COM會(huì )在《COM入門(mén)第二部分》有講解)
導言
COM(Component Object Model,組件對象模型)是個(gè)流行的“三字母縮寫(xiě)詞”,它存在于Windows世界的所有角落,現在每天都有基于COM的巨量的新技術(shù)誕生。本文從最開(kāi)始介紹COM,描述其潛在機制,向你展示如何使用COM組件,讀完本文,你將可以使用Windows內置的和第三方提供的COM組件。本文假定你精通C++,我在例子中使用了一些MFC和ATL,但我會(huì )徹底講解它們,所以就算你不懂MFC和ATL,你可以讀懂,文章段落安排如下:
COM - 它到底是什么?一個(gè)對COM標準的快速介紹,使用COM并不需要懂得這個(gè),但我建議你還是看看以便更好理解;
基本元素的定義 - 講述COM的一些術(shù)語(yǔ);
使用COM - 創(chuàng )建、使用和銷(xiāo)毀COM對象的概覽;
基本接口 - IUnknown,解釋這個(gè)基本接口的方法;
注意事項 - 字符串處理,怎樣處理COM代碼中的字符串;
范例 - 用代碼演示本文所講述的內容;
返回結果(HRESULT)處理 - HRESULT的描述,怎樣根據它來(lái)判斷正確和錯誤;
參考書(shū) - 如果你的雇主需要,你得在這方面多花費一些。:)
COM - 它到底是什么?
COM,簡(jiǎn)單地說(shuō),是一種不同應用程序和不同語(yǔ)言來(lái)共享二進(jìn)制代碼的方法,不同于C++,只是源代碼級的重用。Windows允許你使用DLL實(shí)現二進(jìn)制級的代碼共享,如kernel32.dll,user32.dll等,但因為這都是用C寫(xiě)的DLL,所以它們只能被C或者理解C調用方式的語(yǔ)言所調用。MFC引入了另一種二進(jìn)制級的代碼共享機制--MFC extension DLLs,但這種機制限制更多,你只能在MFC程序中使用它們。而COM通過(guò)建立一種二進(jìn)制的規范來(lái)解決這些問(wèn)題,這也意味著(zhù)COM二進(jìn)制模塊要按照一種特別的結構來(lái)組織,在內存中亦然。規則是語(yǔ)言無(wú)關(guān)的,重擔交給了編譯器。(^o^)COM對象在內存中的組織結構和C++的虛函數一樣,這就是為什么大多數COM代碼都使用C++的原因,但記住,COM確實(shí)是語(yǔ)言無(wú)關(guān)的,因為生成的結果代碼可以被其它所有語(yǔ)言所使用。順便說(shuō),COM不是Win32規范,理論上,它能移植到Unix和其它任意的操作系統,但我沒(méi)見(jiàn)過(guò)Windows世界以外的COM。(譯者:COM是微軟的核心技術(shù)之一,Office,DirectX,.net,到處都是COM,可見(jiàn)微軟熱衷于這項技術(shù)。)
基本元素定義
讓我們從最基礎的開(kāi)始。
接口(interface,譯者:有些地方把Interface譯作“界面”,我認為不妥,因為容易讓人以為是“用戶(hù)界面”)就是一組函數,這些函數稱(chēng)為方法,借口名稱(chēng)帶“I”字母前綴,例如IShellLink。C++中,接口類(lèi)只包含純虛函數。接口可以繼承于其它接口,和C++普通類(lèi)的繼承類(lèi)似,但不允許多重繼承。
CoClass(Component Object Class的縮寫(xiě),譯者:可以翻譯成“COM類(lèi)”,但效果不佳,所以保留英文不作翻譯)包含在一個(gè)DLL或者一個(gè)EXE中,其代碼隱藏在一個(gè)或多個(gè)接口背后,CoClass是接口的實(shí)現,COM對象是CoClass內存中的實(shí)例,注意,CoClass不等于C++的Class,雖然我們經(jīng)常用C++ Class來(lái)實(shí)現一個(gè)CoClass。COM Server(譯者:服務(wù)器,但翻譯為“服務(wù)器”似乎容易引起誤會(huì ),以為是一臺電腦或者某服務(wù)器程序,所以我打算不譯,后面所提及到的Server,一律指COM Server,同理,Client就是COM客戶(hù)端),是個(gè)包含一個(gè)或多個(gè)CoClass的二進(jìn)制文件(DLL或者EXE)。
譯者:一個(gè)COM Server(可以是dll或者exe)可以包含多個(gè)CoClass,而一個(gè)CoClass可以具有多個(gè)接口,一個(gè)接口可以具有多個(gè)方法。(20071105)
注冊是創(chuàng )建注冊表條目來(lái)告訴Windows一個(gè)COM Server在什么地方的過(guò)程。反注冊則相反,移除注冊的條目。
GUID (讀音和“fluid”類(lèi)似,代表全局單一標識) 是一個(gè)128位數字。它作為COM語(yǔ)言無(wú)關(guān)性的一個(gè)標識,每個(gè)接口和CoClass都有GUID,因為GUID是全球唯一,重名可以完全避免(如果你用API去創(chuàng )建它的話(huà)),有時(shí)你會(huì )遇到UUID(Universally Unique Identifier),作用和GUID一樣的。
Class ID,或者稱(chēng)CLSID是CoClass的GUID,Interface ID,或者稱(chēng)IID,是一個(gè)接口的GUID。
GUID在COM中使用如此廣泛有兩個(gè)理由:1、GUID僅僅是個(gè)數字,任何語(yǔ)言都支持;2、GUID是不會(huì )重復的,發(fā)行方便。
HRESULT是COM返回錯誤代碼的完整類(lèi)型,它不是個(gè)句柄(Handle),雖然它有個(gè)“H”前綴,稍后我會(huì )講如何利用它來(lái)檢查執行情況。
最后,COM運行庫(譯者:即COM Library,我認為翻譯作“COM運行庫”比翻譯作“COM庫”更合適)是操作系統與你交互的一部分,當你在做COM相關(guān)的工作時(shí)。通常,COM運行庫又被稱(chēng)為“COM”,但我并不這樣,我怕會(huì )混淆。
使用COM
每種語(yǔ)言都有它處理對象的辦法,例如,C++在棧中建立它們,或動(dòng)態(tài)新建分配它們,由于COM必須語(yǔ)言無(wú)關(guān),COM運行庫提供了它獨有的對象管理機制,下面用C++和它作一下比較:
1、建立一個(gè)新的對象
C++:使用new運算符或者在棧中把對象建立起來(lái)。
COM:調用API或者COM運行庫。
2、刪除一個(gè)對象
C++:使用delete運算符或者讓這個(gè)棧中建立的對象超出有效范圍后自動(dòng)銷(xiāo)毀。
COM:所有的對象都有它們的引用計數,當調用工作完成時(shí)候,調用者必須告訴對象,對象減少引用計數,當引用計數變?yōu)?的時(shí)候,對象銷(xiāo)毀自己。
在創(chuàng )建和銷(xiāo)毀這個(gè)對象之間,你可以使用這個(gè)對象。當你建立一個(gè)COM對象時(shí)候,你告訴COM運行庫,你需要怎樣的接口,當對象創(chuàng )建成功后,COM運行庫返回一個(gè)指向你需要的接口的指針,你可以通過(guò)這個(gè)指針來(lái)調用各種方法,就像它指向的是一個(gè)規則的C++對象。
你可以調用CoCreateInstance()這個(gè)API來(lái)創(chuàng )建一個(gè)COM對象,CoCreateInstance原形如下:
HRESULT CoCreateInstance (
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID* ppv );
參數說(shuō)明:
rclsid - 就是CoClass的CLSID了,例如你可以傳遞CLSID_ShellLink來(lái)表示要創(chuàng )建一個(gè)用于創(chuàng )建快捷方式的COM對象;
pUnkOuter - 這個(gè)參數用于集合COM對象,是給存在的CoClass添加新方法的途徑,我們傳NULL過(guò)去表示我們不使用集合;
dwClsContext - 表示我們要使用的COM Server,本文中,我們將使用最簡(jiǎn)單的Server類(lèi)型--“進(jìn)程內DLL”(in-process DLL,譯者:所謂in-process意思是COM對象存在于調用它的進(jìn)程之中),所以我們傳遞CLSCTX_INPROC_SERVER。提示:你不能使用CLSCTX_ALL(ATL默認),因為這樣將在Windows 95這種沒(méi)安裝DCOM的系統中失??;
riid - 你要返回的接口類(lèi)型ID,例如,你可以傳遞IID_IShellLink表示取得一個(gè)IShellLink接口;
ppv - 接口指針的地址,COM運行庫通過(guò)它返回程序需要的接口。
當你調用CoCreateInstance(),它就在注冊表中尋找這個(gè)CLSID,獲知COM Server的位置,將其加載入內存,然后建立COM對象。
這里有個(gè)例子,用CLSID_ShellLink去獲取一個(gè)IShellLink接口,指向相應的COM對象:
HRESULT hr;
IShellLink* pISL;
hr = CoCreateInstance (CLSID_ShellLink, // (in)CoClass的CLSID
NULL, // (in)集合,這里不使用
CLSCTX_INPROC_SERVER, // (in)Server類(lèi)型為“進(jìn)程內Server”
IID_IShellLink, // (in)接口的IID
(void**) &pISL ); // (out)返回接口指針
if ( SUCCEEDED ( hr ) )
{
// 調用pISL接口的各種方法
}
else
{
// 建立COM實(shí)例失敗,hr保存了出錯值
}
首先我們定義一個(gè)HRESULT來(lái)保存CoCreateInstance()的返回值,用“SUCCEEDED”宏檢查這個(gè)返回值,返回TRUE代表成功,返回FALSE代表失敗,也有一個(gè)對應的宏“FAILED”來(lái)檢測是否失敗。
刪除COM對象
如前面所說(shuō)的,你并不需要自己刪除COM對象,你只需要告訴它,你沒(méi)有再使用它就行了,每個(gè)COM對象都有這個(gè)IUnknown接口,這個(gè)接口有個(gè)Release()函數,當你不再需要使用這個(gè)COM對象的時(shí)候,調用這個(gè)函數。一旦調用了Release(),你就不能再使用這個(gè)接口了,因為COM對象可能已經(jīng)在內存中被銷(xiāo)毀。
如果你的應用程序使用大量不同的COM對象,使用完接口之后調用Release()是非常重要的,如果你不釋放接口,COM對象(包括包含在代碼中的DLL)還將駐留內存,但對你的程序已經(jīng)沒(méi)有任何用處了。如果你的程序需要持續使用很長(cháng)時(shí)間,你應該在程序空閑時(shí)候調用CoFreeUnusedLibraries()這個(gè)API,這個(gè)API將卸載沒(méi)有任何外部引用的COM服務(wù),以此減少應用程序的內存使用。
繼續上面這個(gè)例子,下面說(shuō)明如何使用Release():
// 先根據上述把COM實(shí)例創(chuàng )建好,然后……
if ( SUCCEEDED ( hr ) )
{
// 調用pISL接口的各種方法
// 告訴COM對象,我們使用完了
pISL->Release();
}
IUnknown接口將在下節中詳細講解。
基本接口 - IUnknown
每個(gè)COM接口都從IUnknown繼承,Unknown這個(gè)名字有些誤導人,其實(shí)它并非“不懂”,它指的是如果你有一個(gè)COM對象的IUnknown指針,而你“不懂”這個(gè)COM對象究竟是什么,每個(gè)COM對象都實(shí)現了IUnknown接口。
IUnknown有三個(gè)方法:
1、AddRef() - 告訴COM對象增加其引用計數,如果你獲取一個(gè)接口指針的副本,你應該調用這個(gè)方法;
2、Release() - 告訴COM對象減少其引用計數;
3、QueryInterface() - 從COM對象請求獲取一個(gè)接口指針,如果一個(gè)CoClass有一個(gè)以上的接口,你應該使用這個(gè)方法。
我們已經(jīng)明確看到了Release()的調用,但QueryInterface()呢?當你用CoCreateInstance()創(chuàng )建COM對象的時(shí)候,你直接取得接口的指針,但如果一個(gè)COM對象有一個(gè)以上的接口(IUnknown不算),你就得使用QueryInterface()去取得你想要的接口,QueryInterface()的原形是:
HRESULT IUnknown::QueryInterface(REFIID iid, void** ppv);
參數:
1、iid - 你要獲取的接口的IID;
2、ppv - 指向接口地址的指針,如果QueryInterface()成功取得了接口的話(huà)。
我們繼續“Shell Link”的例子,如果你已經(jīng)有了一個(gè)指向IShellLink接口的指針,pISL,你可以通過(guò)以下代碼取得一個(gè)IPersistFile接口:
HRESULT hr;
IPersistFile* pIPF;
hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );
接下去你可以用SUCCEEDED宏來(lái)檢測QueryInterface()是否成功,如果成功,你就可以使用這個(gè)接口了,和別的接口沒(méi)什么兩樣。當然,在你不再使用它的時(shí)候,調用pIPF->Release()來(lái)釋放它。
注意事項:字符串處理
我們離開(kāi)主題一會(huì )兒,來(lái)討論怎樣處理COM中的字符串,如果你熟悉UNICODE和ANSI,并知道如何轉換它們,你可以跳過(guò)這一節,否則還是閱讀本節吧。
無(wú)論什么時(shí)候COM方法返回一個(gè)string,這個(gè)string都是UNICODE,UNICODE是一種字符編碼方案,其所有字符長(cháng)度都是兩字節,如果你需要讓字符串更加容易管理,你可以將其轉換為T(mén)CHAR字符串。
TCHAR和_t前綴的函數(例如_tcscpy())是為了讓你用同樣的代碼處理Unicode和ANSI準備的,大多數情況下,你都是使用ANSI字符串和ANSI Windows API,所以本文的剩余部分,簡(jiǎn)單起見(jiàn),將用TCHAR來(lái)代替char。
當你從COM返回了Unicode的字符串后,你可以通過(guò)以下途徑將其轉換為char字符串:
1、調用WideCharToMultiByte();
2、調用CRT函數 wcstombs();
3、使用CString構造函數或者運算符(只有MFC有效);
4、使用ATL轉換宏。
WideCharToMultiByte()的原形是:
int WideCharToMultiByte (
UINT CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar );
(譯者:原文在此列舉了WideCharToMultiByte的參數說(shuō)明,我認為沒(méi)有必要,不如自己打開(kāi)MSDN查一下,所以略過(guò))
這里有個(gè)WideCharToMultiByte的例子,其實(shí)它的使用并不像它的參數眾多所顯示出的那么復雜:
//假設我們已經(jīng)有了一個(gè)UNICODE字符串wszSomeString……
char szANSIString [MAX_PATH];
WideCharToMultiByte ( CP_ACP, // ANSI code page
WC_COMPOSITECHECK, // Check for accented characters
wszSomeString, // Source Unicode string
-1, // -1 means string is zero-terminated
szANSIString, // Destination char string
sizeof(szANSIString), // Size of buffer
NULL, // No default character
NULL ); // Don't care about this flag
這么一調用之后,szANSIString就包含了ANSI版本的Unicode字符串。
wcstombs()比較簡(jiǎn)單,卻能取代WideCharToMultiByte(),達到轉換的效果,它的原形是:
size_t wcstombs (
char* mbstr,
const wchar_t* wcstr,
size_t count );
參數是:
mbstr:一個(gè)獲取結果的ANSI緩存;
wcstr:要轉換的UNICODE;
count:mbstr的長(cháng)度。
其實(shí)wcstombs() 是使用了 WC_COMPOSITECHECK | WC_SEPCHARS 標志來(lái)調用 WideCharToMultiByte()的,上面的例子使用wcstombs就變成了:
wcstombs(szANSIString, wszSomeString, sizeof(szANSIString));
MFC的CString類(lèi)包括了接收UNICODE字符串的構造函數和賦值運算符,所以你可以利用CString來(lái)實(shí)現轉換功能,例如:
// 假設我們已經(jīng)有wszSomeString...
CString str1 ( wszSomeString ); // Convert with a constructor.
CString str2;
str2 = wszSomeString; // Convert with an assignment operator.
ATL macros,ATL存在轉換處理功能的宏,將UNICODE轉換為ANSI,就使用W2A()宏,實(shí)際上為了更準確,使用OLE2A()宏更多些,“OLE”表示字符串來(lái)自COM或OLE源,這里有個(gè)例子:
#include <atlconv.h>
// 再次假設我們已經(jīng)有了wszSomeString...
{
char szANSIString [MAX_PATH];
USES_CONVERSION; // Declare local variable used by the macros.
lstrcpy ( szANSIString, OLE2A(wszSomeString) );
}
OLE2A()宏“返回”一個(gè)指向轉換好字符串的指針,但這個(gè)轉換好的字符串是存放在臨時(shí)的棧中變量,所以我們得用lstrcpy來(lái)給它做一個(gè)副本,其它你要關(guān)心的宏還有W2T()(Unicode轉換為T(mén)CHAR),還有W2CT()(Unicode轉換為const TCHAR)。
你可以一直保持用Unicode如果沒(méi)什么特別的要求,如果你要寫(xiě)一個(gè)控制臺應用程序,你可以用std::wcout來(lái)打印Unicode字符串,例如:
wcout<<wszSomeString;
但注意,wcout期望所有串中的字符是Unicode,因此,如果你有“常規”字符,你還是用std::cout來(lái)輸出它吧,如果你有字符串常量,那么用"L"前綴來(lái)使得它們成為Unicode字符串,例如:wcout<<L"The Oracle says..."<<endl<<wszOracleResponse;
使用Unicode有兩點(diǎn)限制:
1、你必須使用wcsXXX()字符串函數來(lái)操作它,比如wcslen();
2、在某些很少出現的情況下,你不可以將一個(gè)Unicode字符串傳遞給Windows 95的Windows API,為了使得代碼在Windows 95和Windows NT中一致,請使用TCHAR類(lèi)型,它在MSDN中有講述。(譯者:Windows 95不支持Unicode,但現在誰(shuí)還在用Windows 95???)
用范例來(lái)總結
下面兩個(gè)例子將展示本文中所提及的COM的概念:
使用COM對象的單接口
第一個(gè)例子向你展示怎樣使用一個(gè)COM對象的單接口,這是你所遇到的最簡(jiǎn)單的例子了。代碼使用了包含在shell中Active Desktop的CoClass來(lái)獲取當前桌面墻紙的文件名,你需要安裝Active Desktop來(lái)讓代碼正常工作。
步驟如下:
1、初始化COM運行庫;
2、建立一個(gè)和Active Desktop交互的COM對象,取得IActiveDesktop接口;
3、調用COM對象的GetWallpaper方法;
4、如果GetWallpaper()調用成功,那么打印墻紙的文件名;
5、釋放接口;
6、釋放COM運行庫。
WCHAR wszWallpaper [MAX_PATH];
CString strPath;
HRESULT hr;
IActiveDesktop* pIAD;
// 初始化COM運行庫(讓W(xué)indows加載一些DLL文件),通常你要在執行其它操作前執行這一步,
// 在MFC程序中,用AfxOleInit()來(lái)替代之,在InitInstance()或者其它啟動(dòng)函數中調用
CoInitialize ( NULL );
//創(chuàng )建COM對象
hr = CoCreateInstance ( CLSID_ActiveDesktop,
NULL,
CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,
(void**) &pIAD );
if ( SUCCEEDED(hr) )
{
//如果成功創(chuàng )建COM對象,我們調用GetWallpaper()方法
hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );
if ( SUCCEEDED(hr) )
{
// 如果成功,打印它返回的文件名
// 注意,我在使用wcout來(lái)顯示wszWallpaper這個(gè)UNICODE字符串
// wcout是UNICODE版的cout
wcout << L"Wallpaper path is:/n" << wszWallpaper << endl << endl;
}
else
{
cout << _T("GetWallpaper() failed.") << endl << endl;
}
// 釋放接口
pIAD->Release();
}
else
{
cout << _T("CoCreateInstance() failed.") << endl << endl;
}
// 釋放COM運行庫,如果是MFC程序,它會(huì )自動(dòng)釋放,無(wú)需手動(dòng)調用
CoUninitialize();
這個(gè)例子中,我使用std::wcout來(lái)顯示Unicode字符串wszWallpaper。
使用COM對象的多接口
第二個(gè)例子向你展示怎樣使用QueryInterface()來(lái)暴露COM對象的接口,代碼使用了shell中的Shell Link coclass來(lái)建立一個(gè)指向上個(gè)例子中我們獲取的墻紙文件的快捷方式。
步驟如下:
1、初始化COM運行庫;
2、建立一個(gè)用來(lái)建立快捷方式的COM對象,并取得IShellLink接口;
3、調用IShellLink接口的SetPath()方法;
4、調用COM對象的QueryInterface()方法來(lái)獲得IPersistFile接口;
5、調用IPersistFile接口的Save()方法;
6、釋放接口;
7、釋放COM運行庫。
CString sWallpaper = wszWallpaper; // wszWallpaper是前面獲取的墻紙文件路徑
IShellLink* pISL;
IPersistFile* pIPF;
// 初始化COM運行庫(讓W(xué)indows加載一些DLL文件),通常你要在執行其它操作前執行這一步,
// 在MFC程序中,用AfxOleInit()來(lái)替代之,在InitInstance()或者其它啟動(dòng)函數中調用
CoInitialize ( NULL );
// 建一個(gè)COM對象,使用Shell提供的“Shell Link”CoClass
// 四個(gè)參數告訴COM我們需要怎樣的COM接口
hr = CoCreateInstance ( CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void**) &pISL );
if ( SUCCEEDED(hr) )
{
// 設置為快捷方式指向的目標為墻紙文件
hr = pISL->SetPath ( sWallpaper );
if ( SUCCEEDED(hr) )
{
// 從COM對象取得第二個(gè)接口--IPersistFile
hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );
if ( SUCCEEDED(hr) )
{
// 調用Save()方法將快捷方式保存到文件,注意該函數第一個(gè)參數是UNICODE字符串
hr = pIPF->Save ( L"C://wallpaper.lnk", FALSE );
// 釋放IPersistFile接口
pIPF->Release();
}
}
// 釋放IShellLink接口
pISL->Release();
}
// 這里省略了出錯處理代碼,讀者自己完成
// 釋放COM運行庫,在MFC應用程序中,就不需要這樣手動(dòng)釋放,MFC會(huì )自動(dòng)完成釋放
CoUninitialize();
結果處理
我已經(jīng)在上面的例子中作了簡(jiǎn)單的出錯處理,使用SUCCEEDED和FAILED宏,現在我來(lái)給出更詳細的處理。
HRESULT是一個(gè)32位有符號整型,以非負代表成功,負數代表失敗,HRESULT有3個(gè)段:標志段(成功或者失敗的標志),設備代碼段和狀態(tài)段,設備代碼段表示HRESULT來(lái)自哪個(gè)組件或者程序,微軟給它每個(gè)組件分配不同的設備代碼,比如COM有一類(lèi)代碼,Task Scheduler有一類(lèi)代碼,等等,這個(gè)代碼是16位長(cháng)度,它沒(méi)有確定的意義,就好像GetLastError()返回的值。
如果你在winerror.h文件中查閱錯誤代碼,你將看到很多HRESULT的列表,大概是“設備代碼段代號_標志段代號_描述”這樣的常量格式,通常,這些HRESULT可以被任何組件返回。如:E_OUTOFMEMORY,它沒(méi)有設備代碼段;REGDB_E_READREGDB:設備代碼段 = REGDB, 指的是注冊表數據庫方面,E = error,READREGDB是錯誤描述(不能讀取數據庫);S_OK:設備代碼段 = 普通;S = 成功,OK是描述,一切正常!
HRESULT列表很多很多,幸運的是我們有簡(jiǎn)單的辦法來(lái)檢測HRESULT的意思,而不需要查閱winerror.h文件,內建的HRESULT可以通過(guò)一個(gè)叫“Error Lookup”的工具來(lái)查詢(xún)其意義,比如你在CoCreateInstance()前忘了調用CoInitialize(),那么CoCreateInstance()就返回代碼0x800401F0,你可以將它輸入到Error Lookup中去,并得知其描述:“CoInitialize沒(méi)有被調用”。

還有種辦法,你可以通過(guò)debuger來(lái)查看HRESULT描述,如果你有個(gè)叫hres的HRESULT,你可以在Watch window中輸入“hres,hr”作為值來(lái)觀(guān)察,“,hr”告訴VC顯示這個(gè)HRESULT值的描述。

參考書(shū)
《Essential COM》,作者:Don Box,ISBN 0-201-63446-5,此書(shū)內容是關(guān)于COM的規范及IDL(Interface Definition Language),書(shū)的前兩章詳細講述了COM的規范和它是為了解決哪些問(wèn)題而設計的。
《MFC Internals》,作者:George Shepherd和Scot Wingo,ISBN 0-201-40721-3,有深度地講述了MFC對COM的支持。
《Beginning ATL 3 COM Programming》,作者:Richard Grimes等,ISBN 1-861001-20-7,這本書(shū)非常有深度地講述了如何用ATL來(lái)編寫(xiě)你的COM組件。
(第一部分完)
聯(lián)系客服