天天看點

COM程式設計入門不得不看的文章 :第一部分 什麼是COM,如何使用COM

       原文:http://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It

       本文的目的是為剛剛接觸COM的程式員提供程式設計指南,并幫助他們了解COM的基本概念。内容包括COM規範簡介,重要的COM術語以及如何重用現有的COM元件。本文不包括如何編寫自己的COM對象和接口。

          COM即元件對象模型,是Component ObjectModel 取前三個字母的縮寫,這三個字母在當今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程式運作的關鍵――重用kernel32.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(簡稱元件對象類――componentobject 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 );
           

以下是參數解釋:

1.rclsid:coclass的CLSID,例如,可以傳遞CLSID_ShellLink建立一個COM對象來建立快捷方式。

2.pUnkOuter:這個參數隻用于COM對象的聚合,利用它向現有的coclass添加新方法。參數值為null表示不使用聚合。

3.dwClsContext:表示所使用COM伺服器的種類。本文使用的是最簡單的COM伺服器,一個程序内(in-process)DLL,

4.        是以傳遞的參數值為CLSCTX_INPROC_SERVER。注意這裡不要随意使用CLSCTX_ALL(在ATL中,它是個預設值),

5.        因為在沒有安裝DCOM的Windows95系統上會導緻失敗。

6.riid:請求接口的IID。例如,可以傳遞IID_IShellLink獲得IShellLink接口指針。

7.ppv:接口指針的位址。COM庫通過這個參數傳回請求的接口。     

          當你調用CoCreateInstance()時,它負責在系統資料庫中查找COM伺服器的位置,将伺服器加載到記憶體,并建立你所請求的coclass執行個體。以下是一個調用的例子,建立一個CLSID_ShellLink對象的執行個體并請求指向這個對象IShellLink接口指針。

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指針。調用CoCreateInstance()來建立新的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()呢?當你用CoCreateInstance()建立對象的時候,你得到一個傳回的接口指針。如果這個COM對象實作一個以上的接口(不包括IUnknown),你就必須用QueryInterface()方法來獲得任何你需要的附加的接口指針。QueryInterface()的原型如下:

HRESULT IUnknown::QueryInterface (
REFIID iid,
void** ppv );
           

以下是參數解釋:

1.iid:所請求的接口的IID。

2.ppv:接口指針的位址,QueryInterface()通過這個參數在成功時傳回這個接口。

        讓我們繼續外殼連結的例子。它實作了IShellLink和IPersistFile接口。如果你已經有一個IShellLink指針,pISL,可以從COM對象請求IPersistFile接口:

HRESULT hr;
IPersistFile* pIPF;
hr = pISL->QueryInterface (IID_IPersistFile, (void**) &pIPF );
           

         然後使用SUCCEEDED宏檢查hr的值以确定QueryInterface()的調用情況,如果成功的話你就可以象使用其它接口指針那樣使用新的接口指針,pIPF。但必須記住調用pIPF->Release()通知COM對象已經用完這個接口。

仔細做好串處理

         這一部分将花點時間來讨論如何在COM代碼中處理串。如果你熟悉Unicode 和ANSI,并知道如何對它們進行轉換的話,你就可以跳過這一部分,否則還是讀一下這一部分的内容。

         不管什麼時候,隻要COM方法傳回一個串,這個串都是Unicode串(這裡指的是寫入COM規範的所有方法)。Unicode是一種字元編碼集,類似ASCII,但用兩個位元組表示一個字元。如果你想更好地控制或操作串的話,應該将它轉換成TCHAR類型串。

          TCHAR和以_t開頭的函數(如_tcscpy())被設計用來讓你用相同的源代碼處理Unicode和ANSI串。在大多數情況下編寫的代碼都是用來處理ANSI串和ANSI WindowsAPIs,是以在下文中,除非另外說明,我所說的字元/串都是指TCHAR類型。你應該熟練掌握TCHAR類型,尤其是當你閱讀其他人寫的有關代碼時,要特别注意TCHAR類型。

          當你從某個COM方法傳回得到一個Unicode串時,可以用下列幾種方法之一将它轉換成char類型串:

  1. 調用 WideCharToMultiByte()API;
  2. 調用CRT 函數wcstombs();
  3. 使用CString 構造器或指派操作(僅用于MFC );
  4. 使用ATL 串轉換宏;

1.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代碼頁。代碼頁是256個字元集。字元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 接受轉換的串的字元緩沖 cbMultiBytelpMultiByteStr的位元組大小。

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版本。調用這個函數後,szANSIString将包含Unicode串的ANSI版本。

2.wcstombs()

      這個CRT函數wcstombs()是個簡化版,但它終結了WideCharToMultiByte()的調用,是以最終結果是一樣的。其原型如下:

size_t wcstombs (
char*         mbstr,
const wchar_t* wcstr,
size_t         count );
           

以下是參數解釋:

1.mbstr:接受結果ANSI串的字元(char)緩沖。

2.wcstr:要轉換的Unicode串。

3.count:mbstr參數所指的緩沖大小。

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

wcstombs ( szANSIString, wszSomeString, sizeof(szANSIString));
           

3.CString

    MFC中的CString包含有構造函數和接受Unicode串的指派操作,是以你可以用CString來實作轉換。例如:

// 假設有一個Unicode串wszSomeString...
 CString str1 ( wszSomeString ); // 用構造器轉換
 CString str2;
 str2 = wszSomeString; // 用指派操作轉換
           

4.ATL宏

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

// 還是假設有一個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,如:

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

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

       必須使用wcsXXX() Unicode串處理函數,如wcslen();

        在Windows 9x環境中不能在Windows API中傳遞Unicode串。要想編寫能在9x和NT上都能運作的應用,必須使用TCHAR類型,詳情請參考MSDN;

用例子代碼總結上述内容

         下面用兩個例子示範本文所講的COM概念。代碼中還包含了本文的例子工程。

使用單接口COM對象

          第一個例子展示的是單接口COM對象。這可能是你碰到得最簡單的例子。它使用外殼中的活動桌面元件對象類(CLSID_ActiveDesktop)來獲得目前桌面牆紙的檔案名。請确認系統中安裝了活動桌面(Active Desktop)。以下是程式設計步驟:

  1. 初始化COM庫。 (Initialize);
  2. 建立一個與活動桌面互動的COM對象,并取得IActiveDesktop接口;
  3. 調用COM對象的GetWallpaper()方法;
  4. 如果GetWallpaper()成功,則輸出/顯示牆紙檔案名;
  5. 釋放接口(Release());
  6. 收回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 pathis:\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元件對象類建立我們在第一個例子中獲得的牆紙檔案的快捷方式。以下是程式設計步驟:

  1. 初始化 COM 庫;
  2. 建立一個用于建立快捷方式的COM 對象并取得IShellLink 接口;
  3. 調用IShellLink 接口的SetPath()方法;
  4. 調用對象的QueryInterface()函數并取得IPersistFile接口;
  5. 調用IPersistFile 接口的Save()方法;
  6. 釋放接口;
  7. 收回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宏進行一些簡單的出錯處理。主要是深入研究從COM方法傳回的HRESULT,以便達到完全了解和熟練應用。

        HRESULT是個32位符号整數,其非負值表示成功,負值表示失敗。HRESULT有三個域:程度位(表示成功或失敗),功能碼和狀态碼。功能碼表示HRESULT來自什麼元件或程式。微軟給不同的元件多賦予功能碼,如:COM、任務排程程式等都有功能碼。功能碼是個16位的值,僅此而已,沒         有其它内在含義;它在數字和意義之間是随意關聯的;類似GetLastError()傳回的值。

        如果你在winerror.h頭檔案中查找錯誤代碼,會看到許多按照[功能]_[程度]_[描述]命名規範列出的HRESULT值,由元件傳回的通用的 HRESULT(類似E_OUTOFMEMORY)在名字中沒有功能碼。如 :

REGDB_E_READREGDB:

功能碼 = REGDB, 指“系統資料庫資料庫(registry database)”;

程度 = E 意思是錯誤(error);

描述 = READREGDB 是對錯誤的描述(意思是不能讀系統資料庫資料庫)。 S_OK: 沒有功能碼――通用(generic)

HRESULT;

程度=S;表示成功(success);

OK 是狀态描述表示一切都好(everything''sOK)。

         好在有一種比察看winerror.h檔案更容易的方法來确定HRESULT的意思。使用VC提供的錯誤查找工具(Error Lookup)可以輕松查到為HRESULT内建功能碼。例如,假設你在CoCreateInstance()之前忘了調用CoInitialize()。CoCreateInstance()傳回的值是0x800401F0。你隻要将這個值輸入到錯誤查找工具按“Look Up”按鈕,便可以看到錯誤資訊描述“尚未調用CoInitialize”如下圖所示:

COM程式設計入門不得不看的文章 :第一部分 什麼是COM,如何使用COM

        另外一種查找HRESULT描述的方法是在調試器中。假設有一個HRESULT變量是hres。在Watch視窗的左邊框中輸入“hres,hr”,表示想要看的值,“hr”便會通知VC顯示HRESULT所描述的值。如下圖所示:

COM程式設計入門不得不看的文章 :第一部分 什麼是COM,如何使用COM

         通過以上的讨論,想必你對COM程式設計有了初步的認識,本文第二部分将探讨COM的内部機制。教你如何用C++編寫自己的接口。

(待續)