Introduction to COM - What It Is and How to Use It.
本文章翻譯自如下鏈接:
http://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It
這篇文章的目的
我寫下這篇文章是為了給那些剛剛接觸 COM 的人提供引導(dǎo),幫助他們理解 COM 的基本概念。這篇文章簡要地介紹了 COM 的一些特性,COM 中的一些術(shù)語,以及如何使用已有的 COM 組件。這篇文章并沒有介紹如何編寫自己的 COM 對象。
介紹
COM(Component Object Model) 組建對象模型這段時間在 Windows 世界中隨處可見。每天都有大堆的基于 COM 的文章冒出來,這些文章拋出來一大堆類似于 COM 對象,接口,服務(wù)器之類的名詞出來,但這些文章都假定你已經(jīng)了解了 COM 并且知道如何使用它。
本文針對于初學(xué)者,由淺入深地介紹 COM 底層機(jī)制,教你如何在程序中使用第三方 COM 對象(以 Windows Shell 為例)。理解了這篇文章的內(nèi)容,你就能使用 Windows 內(nèi)建的 COM 對象和第三方提供的 COM 對象。
本文假定你熟悉 C++, 我在示例代碼中使用了一部分 MFC 和 ATL 的代碼,即使你不了解這兩種技術(shù)也沒關(guān)系,我會在這些地方做出詳細(xì)的解釋。本文分為如下幾個章節(jié):
什么是 COM 簡單介紹 COM 標(biāo)準(zhǔn),COM 的出現(xiàn)解決了什么問題。你使用 COM 并不需要了解這部分的內(nèi)容。但我仍然推薦你讀一下這一章,以便你能夠理解為什么 COM 里的東西為什么要寫成那樣。
基本概念 COM 術(shù)語及其對應(yīng)的意義。
使用 COM 對象 簡要介紹如何創(chuàng)建、使用、銷毀 COM 對象。
基本接口-IUnknown 介紹基本接口 IUnknown, 及該接口中的函數(shù)。
注意-字符串處理 介紹如何在 COM 代碼中處理字符串。
知識點(diǎn)整合-實(shí)例代碼 用兩個代碼實(shí)例來說明本文提到的各個概念。
處理 HRESULT 介紹 HRESULT 類型及如何測試錯誤碼。
引用 介紹一些值得看的書。
什么是 COM
簡單來說, COM 是一種在不同的程序和編程語言間共享二進(jìn)制代碼的方法。這跟 C++ 不一樣,它提倡的是源代碼級別的共享。ATL 就是一個很好的例子,源代碼共享雖然好,但是只能用于 C++。它還帶來了命名空間沖突的可能性,更不用說不斷拷貝重用代碼而導(dǎo)致的工程膨脹。
Windows 使用 DLLs 進(jìn)行二進(jìn)制代碼共享。Windows 應(yīng)用程序通過重用 kernel32.dll,user32.dll 來運(yùn)行。但這些 DLL 都是用 C 寫的,只有符合 C 調(diào)用規(guī)則的語言能夠使用它們。這就給其它語言重用 dll 造成了負(fù)擔(dān)。
MFC 提供了 MFC extension DLLs 這種機(jī)制來共享二進(jìn)制代碼,但它限制更大——這種機(jī)制搞出來的 dll 只能在 MFC 程序之間共享。
COM 通過定義二進(jìn)制標(biāo)準(zhǔn)解決了上述的問題。也就是說 COM 規(guī)定它的二進(jìn)制模塊(dll 和 exe)必須被編譯成能夠跟指定結(jié)構(gòu)相匹配的格式。這種標(biāo)準(zhǔn)也詳細(xì)規(guī)定了如何在內(nèi)存中組織 COM 對象。并且它獨(dú)立于任何編程語言的特性(如C++的命名空間)。一旦確立了上述的規(guī)定,二進(jìn)制模塊就能夠被任何編程語言方便地訪問。這種標(biāo)準(zhǔn)把二進(jìn)制共享所需要做的額外工作放到了編譯器上(而不是 dll 本身),只要編程語言的編譯器產(chǎn)生的二進(jìn)制代碼與標(biāo)準(zhǔn)兼容,其他人就能方便地使用它。
COM 對象在內(nèi)存中的結(jié)構(gòu)“碰巧”和 C++ 虛函數(shù)所使用的結(jié)構(gòu)類似。這也是為什么很多 COM 代碼使用 C++ 進(jìn)行編寫的原因。但要記住,COM 組件用哪種語言寫成并不重要,因?yàn)槿魏握Z言都可以使用它。
順便說一句, COM 不止限于 Windows 平臺。理論上它可以被移植到 Unix 或其它操作系統(tǒng)上。不過我還從來沒在其它系統(tǒng)上看到過有關(guān) COM 的討論。
基本概念
我們從底層開始往上逐個介紹 COM 中的基本概念
interface 接口,在 COM 里表示一組函數(shù)的集合。這些函數(shù)叫做 method. interface 以 I 為開頭,例如 IShellLink. 在 C++ 里, interface 通常被寫作一個只有純虛函數(shù)的抽象基類。類似于 C++, COM 的接口也可以從其它接口那里繼承而來,COM 接口繼承的工作方式和 C++ 單繼承類似,它不允許多繼承。
coclass(Component Object Class組件對象類) 被包含于 DLL 或 EXE 中。它里面包含有一個或多個 interface 的代碼。coclass 也被稱作是這些 interface 的實(shí)現(xiàn)。這里再提醒各位注意一下, COM 中的"類" 并不等同于 C++ 中的"類",盡管實(shí)際工作中通常都會用 C++ 類來編寫 COM 類代碼。
COM Object(COM 對象) 在內(nèi)存中,一個 COM 對象就是一個 coclass 實(shí)例。COM 對象里可能有一個或多個 interface。
COM server (COM 服務(wù)) 是一個包含了一個或多個 coclass 的二進(jìn)制模塊(DLL 或者 EXE)。
Registration 是一個創(chuàng)建注冊表項(xiàng)的過程,它告訴 Windows 如何定位 COM server。Unregistration 反之,它從 Windows 中移除注冊表項(xiàng)。
GUID (Global Unique Identifier) 是一個 128-bit 數(shù)字。GUID 是 COM 提供的一種無關(guān)于編程語言的標(biāo)識方式。每個 interface 和 coclass 都對應(yīng)于一個 GUID。因?yàn)?GUID 具有全球惟一性,所以它可以避免名稱沖突(只要用 COM API 創(chuàng)建,名稱必然不會沖突)。有時候你會看到另一個屬于 UUID (Universally Unique Identifier), 它們倆的功能是一樣的。
-
class ID 或者 CLSID 表示一個 coclass 的 GUID。而 interface ID 或者 IID 表示一個 interface 的 GUID。
GUID 在 COM 里應(yīng)用如此廣泛有如下兩個原因:
GUID 只是一串?dāng)?shù)字,任何編程語言都可以處理它。
不管是任何人任何機(jī)器,一旦 GUID 創(chuàng)建完成后,都是唯一的。因此,COM 開發(fā)者可以創(chuàng)建自己的 GUID 而無需擔(dān)心與其他人的 GUID 發(fā)生沖突。這樣就避免了集中發(fā)布 GUID 的麻煩。
HRESULT 是 COM 中用于返回錯誤碼所使用的一個整型值。盡管以"H"開頭,但它并不是任何對象的“句柄”,接下來的章節(jié)中會有 HRESULT 更詳細(xì)的描述。
COM library 是你在做 COM 相關(guān)操作時,所在的操作系統(tǒng)的一部分。通常 COM library 就被叫做 “COM”,但為了避免產(chǎn)生疑惑,這里沒有采用這種稱呼。
使用 COM 對象
每種編程語言都有它處理對象的方式。比如,在 C++ 里,你可以在棧上創(chuàng)建對象,或者用 new 在堆上動態(tài)分配對象。因?yàn)?COM 必須是語言無關(guān)的,所以 COM library 提供了它自己的對象管理方法。以下列出了 COM 和 C++ 對象管理的區(qū)別:
創(chuàng)建對象
- C++ ,使用 new 操作符或者直接在棧上創(chuàng)建;
- COM ,調(diào)用 COM library 中的 API;
銷毀對象
- C++ ,使用 delete 操作符或者超出作用域自動銷毀棧上的對象;
- COM ,所有對象保存自己的引用計數(shù),當(dāng)使用者用完 COM 對象的時候,它必須告訴 COM 對象遞減引用計數(shù)。當(dāng) COM 對象引用計數(shù)遞減為 0 的時候, COM 對象從內(nèi)存中銷毀。
使用對象 創(chuàng)建完 COM 對象之后,你需要使用這個對象。一個 COM 對象中可能會有一個或多個 method,當(dāng)你要使用某個 method 的時候,你得告訴 COM library 你需要的 interface 是哪個。如果 COM 對象已經(jīng)成功創(chuàng)建,COM library 會返回需要的 interface 的指針。你可以用這個指針來調(diào)用這個 method,就像調(diào)用 C++ 對象的一個接口一樣。
創(chuàng)建 COM 對象
當(dāng)你需要創(chuàng)建一個 COM 對象并獲取對象中的某個接口指針的時候,你可以調(diào)用 COM API CoCreateInstance(). 該函數(shù)的原型如下:
HRESULT CoCreateInstance(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID* ppv );
參數(shù)意義如下:
rclsid :
coclass 的 CLSID. 例如,你可以給這個參數(shù)傳 CLSID_ShellLink 來創(chuàng)建一個 COM 對象,它可以用來創(chuàng)建快捷鍵。
pUnkOuter :
這個參數(shù)只用在 COM 對象的聚合,利用它可以向已有的 coclass 添加新方法。我們這里只需要傳入 NULL 表示不需要聚合。
dwClsContext
指定你需要哪種類型的 COM server。 本文中只是用到了一種最簡單的 server, 進(jìn)程內(nèi) DLL, 所以傳入 CLSCTX_INPROC_SERVER。注意這里不要使用 CLSCTX_ALL(這是 ATL 的默認(rèn)值),因?yàn)樗跊]有安裝 DCOM 的 Windows 95 上會發(fā)生錯誤。
riid :
你希望返回的 interface 的 IID,例如,你可以給這個參數(shù)傳 IID_IShellLink 來獲取 IShellLink 接口。
ppv :
接口指針的地址,COM library 會把請求的接口通過這個參數(shù)來返回。
當(dāng)你調(diào)用 CoCreateInstance() 函數(shù)時,它會在注冊表里查詢 CLSID, 找到 COM server 所在的位置,把 server 加載到內(nèi)存,然后創(chuàng)建一個你所請求的 coclass 的實(shí)例。
如下代碼給出了一個簡單的例子。它會實(shí)例化一個 CLSID_ShellLink 對象并請求一個指向該 COM 對象的 IShellLink 接口:
HRESULT hr;
IShellLink* pISL;
hr = CoCreateInstance( CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void**) &pISL );
if ( SUCCEEDED(hr) )
{
// 創(chuàng)建 COM 對象成功,使用 pISL 調(diào)用接口
}
else
{
// 無法創(chuàng)建 COM 對象,錯誤碼存在 hr 中
}
銷毀 COM 對象
正如之前所說,你不需要手動將 COM 對象從內(nèi)存中銷毀,你只需要告訴它你不再需要它了,所有的 COM 類都繼承自 IUnKnown 接口,這個接口提供了一個函數(shù)叫 Release()。調(diào)用這個函數(shù)就可以告訴 COM 對象你不再需要它。一旦你調(diào)用了 Release(), 你就不能繼續(xù)使用對應(yīng)的接口,因?yàn)?COM 對象可能已經(jīng)從內(nèi)存中銷毀了。
如果你的程序使用了許多不同的 COM 對象,非常重要的一點(diǎn)是,你必須在不需要使用接口時調(diào)用 Release()。如果你沒有釋放這些接口,COM 對象及其對應(yīng)的 dll 會一直存在于內(nèi)存中,這會增加不必要的開銷。如果你的程序要運(yùn)行很長時間,你可以在空閑期調(diào)用 CoFreeUnusedLibraries() API。這個 API 會卸載任何沒有明顯調(diào)用的 COM server。這樣做能在一定程度上減少內(nèi)存占用。
以下示例代碼展示了使用 Release() 的方法:
if ( SUCCEEDED (hr) )
{
// 使用 pISL 接口進(jìn)行一些操作
// 使用完畢,告訴 COM 對象不需要這個接口了
pISL->Release();
}
IUnKnown 接口將在下一節(jié)詳細(xì)介紹。
基本接口-IUnknown
每個 COM 接口都繼承自 IUnknown 接口。 IUnknown 這個名字有點(diǎn)兒誤導(dǎo)人,它的意思并不是說一個“未知的”接口。之所以這樣命名是因?yàn)椋绻阌幸粋€ IUnknown 指針指向一個 COM 對象,你并不知道底層對象是什么,因?yàn)槊總€ COM 對象都實(shí)現(xiàn)了 IUnknown 接口。
IUnknown 接口有三個函數(shù):
- AddRef() 這個接口告知 COM 對象遞增引用計數(shù)。
- Release() 這個接口告知 COM 對象遞減引用計數(shù)。
- QueryInterface() 從 COM 對象中請求接口指針,當(dāng)一個 coclass 實(shí)現(xiàn)了多個接口的時候,你需要用這個函數(shù)來獲取指定的接口。
QueryInterface() 的函數(shù)原型如下:
HRESULT IUnknown::QueryInterface {
REFIID iid,
void** ppv );
參數(shù)意義如下:
iid
你所請求的接口的 IID
ppv
接口指針地址,QueryInterface() 調(diào)用成功的話會將接口通過這個參數(shù)傳遞出來。
在之前的例子里,我們已經(jīng)通過 CoCreateInstance() 獲取了 IShellLink 接口的指針 pISL, 利用 pISL 和 QueryInterface() 還可以獲取 COM 對象里的其它接口指針:
HRESULT hr;
IPersistFile* pIPF;
hr = pISL->QueryInterface( IID_IPersistFile, (void**)pIPF);
你可以用 SUCCEEDED 宏來檢測接口指針是否獲取成功,當(dāng) pIPF 使用完畢的時候,也要像 pISL 那樣調(diào)用 Release() 來遞減引用計數(shù)。
注意-字符串處理
這一節(jié)先繞繞道兒,討論一下如何在 COM 代碼里處理字符串。如果你熟知 Unicode 和 ANSI 字符串的工作原理,并且了解如何在這兩種編碼間進(jìn)行轉(zhuǎn)換,你可以跳過這個章節(jié)。
無論何時,COM 函數(shù)返回的字符串都以 Unicode 進(jìn)行編碼。Unicode 是一個字符編碼集,類似于 ANSI, 只是 Unicode 的所有字符都是兩個字節(jié)。如果你想更好地操作字符串,你可以將它轉(zhuǎn)換成 TCHAR 類型。
TCHAR 和 _t 開頭的函數(shù)(如 _tcscpy) 被設(shè)計成可以用同一份代碼來處理 Unicode 和 ANSI 字符串的形式。在大多數(shù)情況下,你會使用 ANSI 字符串及 ANSI API,因此為了簡便,在往后的文章里,我會使用 char 而非 TCHAR。但你仍然應(yīng)該熟練掌握 TCHAR 類型。
當(dāng)你從 COM 函數(shù)里獲取到一個字符串后,你可以用以下函數(shù)將其轉(zhuǎn)換為 char 類型:
- 調(diào)用 WideCharToMultiByte();
- 調(diào)用 CRT 函數(shù) wcstombs();
- 在 MFC 中可以使用 CString 構(gòu)造函數(shù)來進(jìn)行轉(zhuǎn)換;
- 使用 ATL 字符串轉(zhuǎn)換宏;
以下是這些方法的詳細(xì)介紹:
WideCharToMultiByte()
該函數(shù)的原型如下:
int WideCharToMultiByte(
UINT CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUserDefaultChar );
CodePage
Unicode 字符進(jìn)行轉(zhuǎn)換時的目標(biāo)代碼頁。你可以傳入 CP_ACP 來將 Unicode 轉(zhuǎn)換為當(dāng)前系統(tǒng)所使用的 ANSI 代碼頁。代碼頁是 256 個字符集,其中 0~127 和 ANSI 的一樣,128~255 不一樣,它可以包含圖形字符或讀音符號。每個語言都有它自己的代碼頁,因此使用正確的代碼頁很重要,這樣才能正確地顯示字符。
dwFlags
這個標(biāo)記決定此函數(shù)將如何處理 “復(fù)合的” Unicode 字符串。這種復(fù)合字符串后面會跟一個讀音符號,比如 è。如果指定代碼頁里有這個符號,那就沒啥問題,但如果沒有的話, Windows 必須把這個符號轉(zhuǎn)換為其它的形式進(jìn)行展示。
給 dwFlags 傳入 WC_COMPOSITECHECK, 那么 API 將對 “復(fù)合字符串” 進(jìn)行檢測;
給 dwFlags 傳入 WC_SEPCHARS, 那么 API 會把字符分成 “字符+讀音符號” 的形式,比如 è --> e`
給 dwFlags 傳入 WC_DISCARDNS, 那么 API 會丟棄讀音符號;
給 dwFlags 傳入 WC_DEFAULTCHAR, 那么 API 會將讀音符號替換為一個默認(rèn)符號,這個默認(rèn)符號可以在 lpDefaultChar 中指定。
dwFlags 的默認(rèn)值是 WC_SEPCHARS.
lpWideCharStr
要轉(zhuǎn)換的 Unicode 字符串。
cchWideChar
lpWideCharStr 字符串的長度,如果傳入 -1, 將自動檢查 00 結(jié)尾來確認(rèn)長度。
lpMultiByteStr
char 類型的字符串緩沖區(qū),用于接收轉(zhuǎn)換后的 ANSI 字符串;
cbMultiByte
lpMultiByteStr 緩沖區(qū)的長度,byte 為單位。
lpDefaultChar
可選參數(shù),當(dāng) dwFlags 傳入 WC_COMPOSITECHECK | WC_DEFAULTCHAR 時,如果 API 檢查到某個字符在目標(biāo)代碼頁中不存在,那么將用 這個缺省字符來替換那個字符。這個參數(shù)傳入 NULL 的話,API 會使用系統(tǒng)默認(rèn)字符來替換(一般是一個問號)。
lpUserDefaultChar
可選參數(shù),是一個指向 BOOL 值的指針,如果 lpDefaultChar 被插入到了目標(biāo)字符串中,那么會將這個 BOOL 值設(shè)定為 TRUE, 以此進(jìn)行標(biāo)記。
這個函數(shù)很復(fù)雜,下面是一個例子:
char szANSIString [MAX_PATH];
WideCharToMultiByte( CP_ACP, // 使用系統(tǒng)當(dāng)前代碼頁
WC_COMPOSITECHECK, // 檢查復(fù)合字符
wszSomeString, // 要轉(zhuǎn)換的 Unicode 字符串
-1, // 自動檢查 Unicode 字符串長度
szANSIString, // ANSI 字符串緩沖區(qū)
sizeof(szANSIString), // 緩沖區(qū)長度
NULL, // 使用系統(tǒng)默認(rèn)字符替換復(fù)合字符
NULL ); // 不檢查是否替換
wcstombs()
CRT 函數(shù) wcstombs() 簡單了很多,不過它最終還是在調(diào)用 WideCharToMultiByte().
size_t wcstombs(
char* mbstr,
const wchar_t* wcstr,
size_t count);
mbstr
轉(zhuǎn)換后的 ANSI 字符串存儲在這個緩沖區(qū)中;
wcstr
要轉(zhuǎn)換的 Unicode 字符串;
count
mbstr 的字符串長度, byte 為單位。
wcstombs() 使用了 WC_COMPOSITECHECK | WC_SEPCHARS 標(biāo)記,你可以用如下方式調(diào)用 wcstombs():
wcstombs(szANSIString, wszSomeString, sizeof(szANSIString));
CString
MFC 中的 CString 可以在構(gòu)造函數(shù) 或者 賦值操作符中接受 Unicode 字符串,可以利用這一點(diǎn)來進(jìn)行轉(zhuǎn)換:
CString str1(wszSomeString);
CString str2;
str2 = wszSomeString;
ATL 字符串轉(zhuǎn)換宏
ATL 提供了一組宏用于轉(zhuǎn)換字符串:
- W2A() (Wide To ANSI) 用于將 Unicode 轉(zhuǎn)換為 ANSI;
- OLE2A() (OLE or COM String To ANSI) 跟上面的宏作用一樣,只是描述上更為精確一些,"OLE" 明確指出了是 COM 字符串。
- W2T() (Wide To TCHAR) 將 Unicode 轉(zhuǎn)換為 TCHAR;
- W2CT() (Wide To const TCHAR)
- OLE2CA() (OLE String To const char String)
下面是例子:
char szANSIString[MAX_PATH];
USES_CONVERSION; // 聲明 OLE2A 宏所需要的本地變量
lstrcpy(szANSIString, OLE2A(wszSomeString);
之所以要調(diào)用 lstrcpy 將 OLE2A() 返回的結(jié)果拷貝到 szANSIString, 是因?yàn)?OLE2A() 返回的結(jié)果暫存在棧中,拷貝出來才能長久使用。
知識點(diǎn)整合-實(shí)例代碼
下面用兩個例子來說明本文所提到的各個概念。
使用單接口的 COM 對象
下面的例子使用 shell 中的 Active Desktop coclass 來獲取當(dāng)前壁紙的路徑。
#include <Windows.h>
#include <WinInet.h>
#include <ShlObj.h>
#include <locale>
int main()
{
setlocale(LC_ALL, "chs");
HRESULT hr;
IActiveDesktop* pIAD;
WCHAR wszWallpaper[MAX_PATH];
// 1. 初始化 COM library
CoInitialize(NULL);
// 2. 創(chuàng)建 COM 對象實(shí)例
hr = CoCreateInstance(CLSID_ActiveDesktop,
NULL, CLSCTX_INPROC_SERVER, IID_IActiveDesktop, (void**)&pIAD);
if ( SUCCEEDED(hr) )
{
// 3. 調(diào)用 GetWallpaper 函數(shù)
hr = pIAD->GetWallpaper(wszWallpaper, MAX_PATH, 0);
if ( SUCCEEDED(hr) )
{
wprintf(L"wallpaper path = %s\n", wszWallpaper);
}
else
{
wprintf(L"GetWallpaper Error! \n");
}
// 4. 釋放接口
pIAD->Release();
}
else
{
wprintf(L"CoCreateInstance Error! \n");
}
// 5. 反初始化 COM library
CoUninitialize();
return 0;
}
上面代碼中的 setlocale() 是為了讓 wprintf() 能夠在控制臺中正確輸出中文。
使用多接口的 COM 對象
下面的例子展示了使用 QueryInterface() 獲取接口的方法。它先創(chuàng)建 ShellLink COM 對象,拿到它的 IShellLink 接口,然后調(diào)用 QueryInterface() 獲取 IPersistFile 接口。
這個例子的功能是創(chuàng)建壁紙文件的快捷方式。
#include <Windows.h>
#include <WinInet.h>
#include <ShlObj.h>
int main()
{
WCHAR wszWallpaper[MAX_PATH] = L"C:\\Users\\Dongyu\\Pictures\\桌面黑.png";
// 1. 初始化 COM library
CoInitialize(NULL);
HRESULT hr;
IShellLink* pISL;
IPersistFile* pIPF;
// 2. 創(chuàng)建 ShellLink COM 對象實(shí)例
hr = CoCreateInstance(CLSID_ShellLink,
NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&pISL);
if ( SUCCEEDED(hr) )
{
// 3. 設(shè)定目標(biāo)文件的路徑
hr = pISL->SetPath(wszWallpaper);
if ( SUCCEEDED(hr) )
{
// 4. 獲取第二個 COM 接口
hr = pISL->QueryInterface(IID_IPersistFile, (void**)&pIPF);
if ( SUCCEEDED(hr) )
{
// 5. 調(diào)用 Save 方法保存壁紙的快捷方式
hr = pIPF->Save(L"E:\\wallpaper.lnk", FALSE);
// 6. 用完了,釋放
pIPF->Release();
}
}
// 7. 用完了,釋放
pISL->Release();
}
// 8. 反初始化 COM library
CoUninitialize();
return 0;
}
處理 HRESULT
之前的例子中,用 SUCCEEDED, FAILED 宏簡單處理了 HRESULT. 下面我要介紹一些關(guān)于 HRESULT 更多的細(xì)節(jié)。
HRESULT 返回值是一個 32 位有符號整形。非負(fù)數(shù)表示成功,負(fù)數(shù)表示失敗。
HRESULT 有三個域,[結(jié)果位],[功能碼],[狀態(tài)碼]。結(jié)果位表示結(jié)果是成功還是失敗;功能碼表示錯誤來自于哪個組件,如 COM, 任務(wù)調(diào)度程序都有對應(yīng)的功能碼。功能碼是一個 16 位的值,沒有其它內(nèi)在含義,這個數(shù)字和意義之間是沒有關(guān)聯(lián)的,類似于 GetLastError() 的返回值。
如果你去查看 winerror.h, 你會看到一堆 HRESULT 的定義。它們命名的規(guī)則是: [功能][結(jié)果][描述],如果 HRESULT 不屬于任何特定組件,那么 [功能] 這一項(xiàng)不寫:
- REGDB_E_READREGDB: 功能=REGDB(registry database), 結(jié)果=Error, 描述=READREGDB(表示無法讀取數(shù)據(jù)庫);
- S_OK: 功能=通用, 結(jié)果=Success, 描述=OK(表示沒啥問題);
除了直接查看 winerror.h,你可以用 Error lookup tool 來了解 HRESULT 的具體意義。
你可以在調(diào)試時的監(jiān)視窗口中使用 @err.hr 來查看 HRESULT 所代表的具體意義。
引用
Essential COM by Don Box《COM 本質(zhì)論》
MFC Internals by George Shepherd and Scot Wingo
Beginning ATL 3 COM Programming by Richard Grimes
以上是文章中推薦的幾本書,《COM 本質(zhì)論》值得一讀,但有點(diǎn)難懂。
《COM 技術(shù)內(nèi)幕》 也值得一讀。