COM編程入門

 

第一部分——什麼是COM,如何使用COM

編譯:趙湘寧程序員


本文的目的是爲剛剛接觸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是一種跨應用和語言共享二進制代碼的方法。與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(簡稱組件對象類——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自己。可是爲了不混淆才分開描述的。ide

使用和處理COM對象函數

  每一種語言都有其本身處理對象的方式。例如,C++是在棧中建立對象,或者用new動態分配。由於COM必須獨立於語言,因此COM庫爲本身提供對象管理例程。下面是對COM對象管理和C++對象管理所作的一個比較:工具

建立一個新對象ui

C++中,用new操做符,或者在棧中建立對象。
COM中,調用COM庫中的API。this

刪除對象

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-process)DLL,
        因此傳遞的參數值爲CLSCTX_INPROC_SERVER。注意這裏不要隨意使用CLSCTX_ALL(在ATL中,它是個缺省值),
        由於在沒有安裝DCOM的Windows95系統上會致使失敗。
riid:請求接口的IID。例如,能夠傳遞IID_IShellLink得到IShellLink接口指針。
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 );
如下是參數解釋:
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->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 串轉換宏;
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 接受轉換的串的字符緩衝 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版本。 調用這個函數後,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 <atlconv.h>

// 仍是假設有一個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宏進行一些簡單的出錯處理。主要是深刻研究從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''s OK)。

好在有一種比察看winerror.h文件更容易的方法來肯定HRESULT的意思。使用VC提供的錯誤查找工具(Error Lookup)能夠輕鬆查到爲HRESULT內建功能碼。例如,假設你在CoCreateInstance()以前忘了調用CoInitialize()。CoCreateInstance()返回的值是0x800401F0。你只要將這個值輸入到錯誤查找工具按「Look Up」按鈕,即可以看到錯誤信息描述「還沒有調用CoInitialize」以下圖所示:

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

經過以上的討論,想必你對COM編程有了初步的認識,本文第二部分將探討COM的內部機制。教你如何用C++編寫本身的接口。 

原文引自:http://www.vckbase.com/document/viewdoc/?id=212

相關文章
相關標籤/搜索