COM入門(轉載)

mantian發表於2005-09-01

留著,早晚要看~

[@more@]本文的目的是為剛剛接觸COM的程式設計師提供程式設計指南,並幫助他們理解COM的基本概念。
內容包括COM規範簡介,重要的COM術語以及如何重用現有的COM元件。本文不包括如何編
寫自己的COM物件和介面。
COM即元件物件模型,是Component Object Model 取前三個字母的縮寫,這三個字母在
當今Windows的世界中隨處可見。隨時湧現出來的大把大把的新技術都以COM為基礎。各
種文件中也充斥著諸如COM物件、介面、伺服器之類的術語。因此,對於一個程式設計師來說
,不僅要掌握使用COM的方法,而且還要徹底熟悉COM的所有一切。
本文由淺入深描述COM的內在執行機制,教你如何使用第三方提供的COM物件(以Windows
外殼元件Shell為例)。讀完本文後,你就能掌握如何使用Windows作業系統中內建的組
件和第三方提供的COM物件。
本文假設你精通C++語言。在例子程式碼中使用了一點MFC和ATL,如果你不熟悉MFC和ATL也
沒關係,本文會對這些程式碼進行完全透徹的解釋。
本文包括以下幾個部分:
COM——到底是什麼?——COM標準的要點介紹,它被設計用來解決什麼問題?
基本元素的定義——COM術語以及這些術語的含義。
使用和處理COM物件——如何建立、使用和銷燬COM物件。
基本介面——描述IUnknown基本介面及其方法。
掌握串的處理——在COM程式碼中如何處理串。
應用COM技術——例子程式碼,舉例說明本文所討論的所有概念。
處理HRESULT——HRESULT型別描述,如何監測錯誤及成功程式碼。

COM——到底是什麼?
簡單地說,COM是一種跨應用和語言共享二進位制程式碼的方法。與C++不同,它提倡原始碼
重用。ATL便是一個很好的例證。原始碼級重用雖然好,但只能用於C++。它還帶來了名字
衝突的可能性,更不用說不斷複製重用程式碼而導致工程膨脹和臃腫。
Windows使用DLLs在二進位制級共享程式碼。這也是Windows程式執行的關鍵——重用kernel3
2.dll, user32.dll等。但DLLs是針對C介面而寫的,它們只能被C或理解C呼叫規範的語
言使用。由程式語言來負責實現共享程式碼,而不是由DLLs本身。這樣的話DLLs的使用受
到限制。
MFC引入了另外一種MFC擴充套件DLLs二進位制共享機制。但它的使用仍受限制——只能在MFC程
序中使用。
COM透過定義二進位制標準解決了這些問題,即COM明確指出二進位制模組(DLLs和EXEs)必
須被編譯成與指定的結構匹配。這個標準也確切規定了在記憶體中如何組織COM物件。COM
定義的二進位制標準還必須獨立於任何程式語言(如C++中的命名修飾)。一旦滿足了這些
條件,就可以輕鬆地從任何程式語言中存取這些模組。由編譯器負責所產生的二進位制代
碼與標準相容。這樣使後來的人就能更容易地使用這些二進位制程式碼。
在記憶體中,COM物件的這種標準形式在C++虛擬函式中偶爾用到,所以這就是為什麼許多COM
程式碼使用C++的原因。但是記住,編寫模組所用的語言是無關的,因為結果二進位制程式碼為
所有語言可用。
此外,COM不是Win32特有的。從理論上講,它可以被移植到Unix或其它作業系統。但是
我好像還從來沒有在Windows以外的地方聽說過COM。
基本元素的定義
我們從下往上看。介面只不過是一組函式。這些函式被稱為方法。介面名字以大寫的I開
頭,例如C++中的IShellLink,介面被設計成一個抽象基類,其中只有純粹的虛擬函式。
介面可以從其它介面繼承,這裡所說的繼承的原理就好像C++中的單繼承。介面是不允許
多繼承的。
coclass(簡稱元件物件類——component object class)被包含在DLL或EXE中,並且包
含著一個或者多個介面的程式碼。元件物件類(coclasss)實現這些介面。COM物件在記憶體
中表現為元件物件類(coclasss)的一個例項。注意COM“類”和C++“類”是不相同的
,儘管常常COM類實現的就是一個C++類。
COM伺服器是包含了一個或多個coclass的二進位制(DLL或EXE)。
註冊(Registration)是建立登錄檔入口的一個過程,告訴Windows 作業系統COM伺服器
放在什麼位置。取消註冊(Unregistration)則相反——從登錄檔刪除這些註冊入口。
GUID(諧音為“fluid”,意思是全球唯一標示符——globally unique identifier)是
個128位的數字。它是一種獨立於COM程式語言的標示方法。每一個介面和coclass有一個
GUID。因為每一個GUID都是全球唯一的,所以避免了名字衝突(只要你用COM API建立它
們)。有時你還會碰到另一個術語UUID(意思也是全球唯一標示符——universally
unique identifier)。UUIDs和GUIDs在實際使用時的用途是一樣的。
類ID或者CLSID是命名coclass的GUID。介面ID或者IID是命名介面的GUID。
在COM中廣泛地使用GUID有兩個理由:
1、GUIDs只是簡單的數字,任何程式語言都可以對之進行處理。
2、GUIDs可以在任何機器上被任何人建立,一旦完成建立,它就是唯一的。因此,COM開
發人員可以建立自己特有的GUIDs而不會與其它開發人員所建立的GUIDs有衝突。這樣就
消除了集中授權釋出GUIDs的必要。
HRESULT是COM用來返回錯誤和成功程式碼的整型數字。除此之外,別無它意,雖然以H作前
綴,但沒有控制程式碼之意。下文會對它有更多的討論。
最後,COM庫是在你使用COM時與你互動的作業系統的一部分,它常常指的就是COM本身。
但是為了避免混淆才分開描述的。
使用和處理COM物件
每一種語言都有其自己處理物件的方式。例如,C++是在棧中建立物件,或者用new動態
分配。因為COM必須獨立於語言,所以COM庫為自己提供物件管理例程。下面是對COM物件
管理和C++物件管理所做的一個比較:
建立一個新物件
C++中,用new運算子,或者在棧中建立物件。
COM中,呼叫COM庫中的API。
刪除物件
C++中,用delete運算子,或將棧物件踢出。
COM中,所有的物件保持它們自己的引用計數。呼叫者必須通知物件什麼時候用完這個對
象。當引用計數為零時,COM物件將自己從記憶體中釋放。
由此可見,物件處理的兩個階段:建立和銷燬,缺一不可。當建立COM物件時要通知COM
庫使用哪一個介面。如果這個物件建立成功,COM庫返回所請求介面的指標。然後透過這
個指標呼叫方法,就像使用常規C++物件指標一樣。
建立COM物件
為了建立COM物件並從這個物件獲得介面,必須呼叫COM庫的API函式,CoCreateInstance
()。其原型如下:
HRESULT CoCreateInstance (
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID* ppv );

以下是引數解釋:
rclsid
coclass的CLSID,例如,可以傳遞CLSID_ShellLink建立一個COM物件來建立快捷方式。
pUnkOuter
這個引數只用於COM物件的聚合,利用它向現有的coclass新增新方法。引數值為null表
示不使用聚合。
dwClsContext
表示所使用COM伺服器的種類。本文使用的是最簡單的COM伺服器,一個程式內(in-proc
ess)DLL,所以傳遞的引數值為CLSCTX_INPROC_SERVER。注意這裡不要隨意使用CLSCTX_
ALL(在ATL中,它是個預設值),因為在沒有安裝DCOM的Windows95系統上會導致失敗。

riid
請求介面的IID。例如,可以傳遞IID_IShellLink獲得IShellLink介面指標。
ppv
介面指標的地址。COM庫透過這個引數返回請求的介面。
當你呼叫CoCreateInstance()時,它負責在登錄檔中查詢COM伺服器的位置,將伺服器加
載到記憶體,並建立你所請求的coclass例項。
以下是一個呼叫的例子,建立一個CLSID_ShellLink物件的例項並請求指向這個物件IShe
llLink介面指標。
HRESULT hr;
IShellLink* pISL;

hr = CoCreateInstance ( CLSID_ShellLink, // coclass 的CLSID
NULL, // 不是用聚合
CLSCTX_INPROC_SERVER, // 伺服器型別
IID_IShellLink, // 介面的IID
(void**) &pISL ); // 指向介面的指標

if ( SUCCEEDED ( hr ) )
{
// 用pISL呼叫方法
}
else
{
// 不能建立COM物件,hr 為出錯程式碼
}

首先宣告一個接受CoCreateInstance()返回值的HRESULT和IShellLink指標。呼叫CoCrea
teInstance()來建立新的COM物件。如果hr接受到一個表示成功的程式碼,則SUCCEEDED宏
返回TRUE,否則返回FALSE。FAILED是一個與SUCCEEDED對應的宏用來檢查失敗程式碼。
刪除COM物件
前面說過,你不用釋放COM物件,只要告訴它們你已經用完物件。IUnknown是每一個COM
物件必須實現的介面,它有一個方法,Release()。呼叫這個方法通知COM物件你不再需
要物件。一旦呼叫了這個方法之後,就不能再次使用這個介面,因為這個COM物件可能從
此就從記憶體中消失了。
如果你的應用程式使用許多不同的COM物件,因此在用完某個介面後呼叫Release()就顯
得非常重要。如果你不釋放介面,這個COM物件(包含程式碼的DLLs)將保留在記憶體中,這
會增加不必要的開銷。如果你的應用程式要長時間執行,就應該在應用程式處於空閒期
間呼叫CoFreeUnusedLibraries() API。這個API將解除安裝任何沒有明顯引用的COM伺服器,
因此這也降低了應用程式使用的記憶體開銷。
繼續用上面的例子來說明如何使用Release():
// 像上面一樣建立COM 物件, 然後,

if ( SUCCEEDED ( hr ) )
{
// 用pISL呼叫方法

// 通知COM 物件不再使用它
pISL->Release();
}

接下來將詳細討論IUnknown介面。
基本介面——IUnknown
每一個COM介面都派生於IUnknown。這個名字有點誤導人,其中沒有未知(Unknown)接
口的意思。它的原意是如果有一個指向某COM物件的IUnknown指標,就不用知道潛在的對
象是什麼,因為每個COM物件都實現IUnknown。

IUnknown 有三個方法:

AddRef() – 通知COM物件增加它的引用計數。如果你進行了一次介面指標的複製,就必
須呼叫一次這個方法,並且原始的值和複製的值兩者都要用到。在本文的例子中沒有用
到AddRef()方法。
Release() – 通知COM物件減少它的引用計數。參見前面的Release()示例程式碼段。
QueryInterface() – 從COM物件請求一個介面指標。當coclass實現一個以上的介面時
,就要用到這個方法。
前面已經看到了Release()的使用,但如何使用QueryInterface()呢?當你用CoCreateIns
tance()建立物件的時候,你得到一個返回的介面指標。如果這個COM物件實現一個以上
的介面(不包括IUnknown),你就必須用QueryInterface()方法來獲得任何你需要的附
加的介面指標。QueryInterface()的原型如下:
HRESULT IUnknown::QueryInterface (
REFIID iid,
void** ppv );


以下是引數解釋:
iid
所請求的介面的IID。
ppv
介面指標的地址,QueryInterface()透過這個引數在成功時返回這個介面。
讓我們繼續外殼連結的例子。它實現了IShellLink 和IPersistFile介面。如果你已經有
一個IShellLink指標,pISL,可以從COM物件請求IPersistFile介面:
HRESULT hr;
IPersistFile* pIPF;
hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );

然後使用SUCCEEDED宏檢查hr的值以確定QueryInterface()的呼叫情況,如果成功的話你
就可以象使用其它介面指標那樣使用新的介面指標,pIPF。但必須記住呼叫pIPF->Relea
se()通知COM物件已經用完這個介面。
仔細做好串處理
這一部分將花點時間來討論如何在COM程式碼中處理串。如果你熟悉Unicode 和ANSI,並知
道如何對它們進行轉換的話,你就可以跳過這一部分,否則還是讀一下這一部分的內容

不管什麼時候,只要COM方法返回一個串,這個串都是Unicode串(這裡指的是寫入COM規
範的所有方法)。Unicode是一種字元編碼集,類似ASCII,但用兩個位元組表示一個字元
。如果你想更好地控制或操作串的話,應該將它轉換成TCHAR型別串。
TCHAR和以_t開頭的函式(如_tcscpy())被設計用來讓你用相同的原始碼處理Unicode和
ANSI串。在大多數情況下編寫的程式碼都是用來處理ANSI串和ANSI WindowsAPIs,所以在
下文中,除非另外說明,我所說的字元/串都是指TCHAR型別。你應該熟練掌握TCHAR型別
,尤其是當你閱讀其他人寫的有關程式碼時,要特別注意TCHAR型別。
當你從某個COM方法返回得到一個Unicode串時,可以用下列幾種方法之一將它轉換成cha
r型別串:

1、呼叫 WideCharToMultiByte() API。
2、呼叫CRT 函式wcstombs()。
3、使用CString 構造器或賦值操作(僅用於MFC )。
4、使用ATL 串轉換宏。
WideCharToMultiByte()
你可以用WideCharToMultiByte()將一個Unicode串轉換成一個ANSI串。此函式的原型如
下:
int WideCharToMultiByte (
UINT CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar );


以下是引數解釋:
CodePage
Unicode字元轉換成的內碼表。你可以傳遞CP_ACP來使用當前的ANSI內碼表。內碼表是25
6個字符集。字元0——127與ANSI編碼一樣。字元128——255與ANSI字元不同,它可以包
含圖形字元或者讀音符號。每一種語言或地區都有其自己的內碼表,所以使用正確的代
碼頁對於正確地顯示重音字元很重要。
dwFlags
dwFlags 確定Windows如何處理“複合” Unicode字元,它是一種後面帶讀音符號的字元
。如è就是一個複合字元。如果這些字元在CodePage引數指定的內碼表中,不會出什麼
事。否則,Windows必須對之進行轉換。
傳遞WC_COMPOSITECHECK使得這個API檢查非對映覆合字元。
傳遞WC_SEPCHARS使得Windows將字元分為兩段,即字元加讀音,如e`。
傳遞WC_DISCARDNS使得Windows丟棄讀音符號。
傳遞WC_DEFAULTCHAR使得Windows用lpDefaultChar引數中說明的預設字元替代複合字元

預設行為是WC_SEPCHARS。
lpWideCharStr
要轉換的Unicode串。
cchWideChar
lpWideCharStr在Unicode 字元中的長度。通常傳遞-1,表示這個串是以0x00結尾。
lpMultiByteStr
接受轉換的串的字元緩衝
cbMultiByte
lpMultiByteStr的位元組大小。
lpDefaultChar
可選——當dwFlags包含WC_COMPOSITECHECK | WC_DEFAULTCHAR並且某個Unicode字元不
能被對映到同等的ANSI串時所傳遞的一個單字元ANSI串,包含被插入的“預設”字元。
可以傳遞NULL,讓API使用系統預設字元(一種寫法是一個問號)。
lpUsedDefaultChar
可選——指向BOOL型別的一個指標,設定它來表示是否預設字元曾被插入ANSI串。可以
傳遞NULL來忽略這個引數。
我自己都有點暈菜了……!,萬事開頭難啊……,不搞清楚這些東西就很難搞清楚COM的
串處理。何況文件中列出的比實際應用的要複雜得多。下面就給出瞭如何使用這個API的
例子:
// 假設已經有了一個Unicode 串 wszSomeString...
char szANSIString [MAX_PATH];

WideCharToMultiByte ( CP_ACP, // ANSI 內碼表
WC_COMPOSITECHECK, // 檢查重音字元
wszSomeString, // 原Unicode 串
-1, // -1 意思是串以0x00結尾
szANSIString, // 目的char字串
sizeof(szANSIString), // 緩衝大小
NULL, // 肥預設字串
NULL ); // 忽略這個引數
呼叫這個函式後,szANSIString將包含Unicode串的ANSI版本。

wcstombs()
這個CRT函式wcstombs()是個簡化版,但它終結了WideCharToMultiByte()的呼叫,所以
最終結果是一樣的。其原型如下:
size_t wcstombs (
char* mbstr,
const wchar_t* wcstr,
size_t count );

以下是引數解釋:
mbstr
接受結果ANSI串的字元(char)緩衝。
wcstr
要轉換的Unicode串。
count
mbstr引數所指的緩衝大小。

wcstombs()在它對WideCharToMultiByte()的呼叫中使用WC_COMPOSITECHECK |
WC_SEPCHARS標誌。用wcstombs()轉換前面例子中的Unicode串,結果一樣:

wcstombs ( szANSIString, wszSomeString, sizeof(szANSIString) );
CString
MFC中的CString包含有建構函式和接受Unicode串的賦值操作,所以你可以用CString來
實現轉換。例如:

// 假設有一個Unicode串wszSomeString...

CString str1 ( wszSomeString ); // 用構造器轉換
CString str2;

str2 = wszSomeString; // 用賦值操作轉換

ATL宏
ATL有一組很方便的宏用於串的轉換。W2A()用於將Unicode串轉換為ANSI串(記憶方法是
“wide to ANSI”——寬字元到ANSI)。實際上使用OLE2A()更精確,“OLE”表示的意
思是COM串或者OLE串。下面是使用這些宏的例子:

#include

// 還是假設有一個Unicode串wszSomeString...

{
char szANSIString [MAX_PATH];
USES_CONVERSION; // 宣告這個宏要使用的區域性變數

lstrcpy ( szANSIString, OLE2A(wszSomeString) );
}

OLE2A()宏“返回”轉換的串的指標,但轉換的串被儲存在某個臨時棧變數中,所以要用
lstrcpy()來獲得自己的複製。其它的幾個宏是W2T()(Unicode 到 TCHAR)以及W2CT()
(Unicode到常量TCHAR串)。
有個宏是OLE2CA()(Unicode到常量char串),可以被用到上面的例子中,OLE2CA()實際
上是個更正宏,因為lstrcpy()的第二個引數是一個常量char*,關於這個問題本文將在
以後作詳細討論。
另一方面,如果你不想做以上覆雜的串處理,儘管讓它還保持為Unicode串,如果編寫的
是控制檯應用程式,輸出/顯示Unicode串時應該用全程變數std::wcout,如:

wcout << wszSomeString;

但是要記住,std::wcout只認Unicode,所以你要是“正常”串的話,還得用std::cout
輸出/顯示。對於Unicode串文字量,要使用字首L標示,如:

wcout << L"The Oracle says..." << endl << wszOracleResponse;

如果保持串為Unicode,程式設計時有兩個限制:

—— 必須使用wcsXXX() Unicode串處理函式,如wcslen()。
—— 在Windows 9x環境中不能在Windows API中傳遞Unicode串。要想編寫能在9x和NT上
都能執行的應用,必須使用TCHAR型別,詳情請參考MSDN。

用例子程式碼總結上述內容
下面用兩個例子演示本文所講的COM概念。程式碼中還包含了本文的例子工程。
使用單介面COM物件
第一個例子展示的是單介面COM物件。這可能是你碰到得最簡單的例子。它使用外殼中的
活動桌面元件物件類(CLSID_ActiveDesktop)來獲得當前桌面牆紙的檔名。請確認系
統中安裝了活動桌面(Active Desktop)。

以下是程式設計步驟:

初始化COM庫。 (Initialize)
建立一個與活動桌面互動的COM物件,並取得IActiveDesktop介面。
呼叫COM物件的GetWallpaper()方法。
如果GetWallpaper()成功,則輸出/顯示牆紙檔名。
釋放介面(Release())。
收回COM庫(Uninitialize)。

WCHAR wszWallpaper [MAX_PATH];
CString strPath;
HRESULT hr;
IActiveDesktop* pIAD;

// 1. 初始化COM庫(讓Windows載入DLLs)。通常是在程式的InitInstance()中調

// CoInitialize ( NULL )或其它啟動程式碼。MFC程式使用AfxOleInit()。

CoInitialize ( NULL );

// 2. 使用外殼提供的活動桌面元件物件類建立COM物件。
// 第四個引數通知COM需要什麼介面(這裡是IActiveDesktop).

hr = CoCreateInstance ( CLSID_ActiveDesktop,
NULL,
CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,
(void**) &pIAD );

if ( SUCCEEDED(hr) )
{
// 3. 如果COM物件被建立成功,則呼叫這個物件的GetWallpaper() 方法。
hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );

if ( SUCCEEDED(hr) )
{
// 4. 如果 GetWallpaper() 成功,則輸出它返回的檔名字。
// 注意這裡使用wcout 來顯示Unicode 串wszWallpaper. wcout 是
// Unicode 專用,功能與cout.相同。
wcout << L"Wallpaper path is:n " << wszWallpaper << endl <<
endl;
}
else
{
cout << _T("GetWallpaper() failed.") << endl << endl;
}

// 5. 釋放介面。
pIAD->Release();
}
else
{
cout << _T("CoCreateInstance() failed.") << endl << endl;
}

// 6. 收回COM庫。MFC 程式不用這一步,它自動完成。
CoUninitialize();

在這個例子中,輸出/顯示Unicode 串 wszWallpaper用的是std::wcout。

使用多介面的COM物件
第二個例子展示瞭如何使用一個提供單介面的COM物件QueryInterface()函式。其中的代
碼用外殼的Shell Link元件物件類建立我們在第一個例子中獲得的牆紙檔案的快捷方式

以下是程式設計步驟:

初始化COM 庫。
建立一個用於建立快捷方式的COM 物件並取得IShellLink 介面。
呼叫IShellLink 介面的SetPath()方法
呼叫物件的QueryInterface()函式並取得IPersistFile介面。
呼叫IPersistFile 介面的Save()方法。
釋放介面
收回COM庫

CString sWallpaper = wszWallpaper; // 將牆紙路徑轉換為ANSI
IShellLink* pISL;
IPersistFile* pIPF;

// 1. 初始化COM庫(讓Windows 載入DLLs). 通常在InitInstance()中呼叫
// CoInitialize ( NULL )或其它啟動程式碼。MFC 程式使用AfxOleInit() 。

CoInitialize ( NULL );

// 2. 使用外殼提供的Shell Link元件物件類建立COM物件。.
// 第四個引數通知COM 需要什麼介面(這裡是IShellLink)。

hr = CoCreateInstance ( CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void**) &pISL );

if ( SUCCEEDED(hr) )
{
// 3. 設定快捷方式目標(牆紙檔案)的路徑。
hr = pISL->SetPath ( sWallpaper );

if ( SUCCEEDED(hr) )
{
// 4. 獲取這個物件的第二個介面(IPersistFile)。
hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );

if ( SUCCEEDED(hr) )
{
// 5. 呼叫Save() 方法儲存某個檔案得快捷方式。第一個引數是
// Unicode 串。
hr = pIPF->Save ( L"C:wallpaper.lnk", FALSE );

// 6a. 釋放IPersistFile 介面。
pIPF->Release();
}
}

// 6. 釋放IShellLink 介面。
pISL->Release();
}

// 輸出錯誤資訊部分這裡省略。

// 7. 收回COM 庫。MFC 程式不用這一步,它自動完成。
CoUninitialize();

處理HRESULT
這一部分準備用SUCCEEDED 和 FAILED宏進行一些簡單的出錯處理。主要是深入研究從CO
M方法返回的HRESULT,以便達到完全理解和熟練應用。
HRESULT是個32位符號整數,其非負值表示成功,負值表示失敗。HRESULT有三個域:程
度位(表示成功或失敗),功能碼和狀態碼。功能碼錶示HRESULT來自什麼元件或程式。
微軟給不同的元件多賦予功能碼,如:COM、任務排程程式等都有功能碼。功能碼是個16
位的值,僅此而已,沒有其它內在含義;它在數字和意義之間是隨意關聯的;類似GetLa
stError()返回的值。
如果你在winerror.h標頭檔案中查詢錯誤程式碼,會看到許多按照[功能]_[程度]_[描述]命
名規範列出的HRESULT值,由元件返回的通用的HRESULT(類似E_OUTOFMEMORY)在名字中
沒有功能碼。如,
REGDB_E_READREGDB: 功能碼 = REGDB, 指“登錄檔資料庫(registry database)”;
程度 = E 意思是錯誤(error);描述 = READREGDB 是對錯誤的描述(意思是不能讀注
冊表資料庫)。
S_OK: 沒有功能碼——通用(generic)HRESULT;程度=S;表示成功(success);OK
是狀態描述表示一切都好(everything's OK)。
好在有一種比察看winerror.h檔案更容易的方法來確定HRESULT的意思。使用VC提供的錯
誤查詢工具(Error Lookup)可以輕鬆查到為HRESULT內建功能碼。例如,假設你在CoCr
eateInstance()之前忘了呼叫CoInitialize()。CoCreateInstance()返回的值是0x80040
1F0。你只要將這個值輸入到錯誤查詢工具按“Look Up”按鈕,便可以看到錯誤資訊描
述“尚未呼叫CoInitialize”如下圖所示:

另外一種查詢HRESULT描述的方法是在偵錯程式中。假設有一個HRESULT變數是hres。在Wat
ch視窗的左邊框中輸入“hres,hr”,表示想要看的值,“hr”便會通知VC顯示HRESULT
所描述的值。如下圖所示:


透過以上的討論,想必你對COM程式設計有了初步的認識,本文第二部分將探討COM的內部機
制。教你如何用C++編寫自己的介面。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/559014/viewspace-805601/,如需轉載,請註明出處,否則將追究法律責任。

相關文章