青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

C++ Programmer's Cookbook

{C++ 基礎} {C++ 高級} {C#界面,C++核心算法} {設計模式} {C#基礎}

COM編程入門(1)

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標準的要點介紹,它被設計用來解決什么問題?
基本元素的定義——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(簡稱組件對象類——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-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版本。
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”如下圖所示:
 [Error Lookup screen shot - 7K]
另外一種查找HRESULT描述的方法是在調試器中。假設有一個HRESULT變量是hres。在Watch窗口的左邊框中輸入“hres,hr”,表示想要看的值,“hr”便會通知VC顯示HRESULT所描述的值。如下圖所示:
 [Watch window - 4K]
   通過以上的討論,想必你對COM編程有了初步的認識,本文第二部分將探討COM的內部機制。教你如何用C++編寫自己的接口。
v

posted on 2005-12-23 13:08 夢在天涯 閱讀(3172) 評論(0)  編輯 收藏 引用 所屬分類: COM/ATL

公告

EMail:itech001#126.com

導航

統計

  • 隨筆 - 461
  • 文章 - 4
  • 評論 - 746
  • 引用 - 0

常用鏈接

隨筆分類

隨筆檔案

收藏夾

Blogs

c#(csharp)

C++(cpp)

Enlish

Forums(bbs)

My self

Often go

Useful Webs

Xml/Uml/html

搜索

  •  

積分與排名

  • 積分 - 1811983
  • 排名 - 5

最新評論

閱讀排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
      <noscript id="pjuwb"></noscript>
            <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
              <dd id="pjuwb"></dd>
              <abbr id="pjuwb"></abbr>
              亚洲欧美中文日韩v在线观看| 亚洲国产导航| 亚洲一区二区在| 国产乱子伦一区二区三区国色天香 | 欧美华人在线视频| 一区二区三区日韩精品| 99国产精品久久久久久久成人热| 欧美日韩在线视频一区二区| 午夜精品久久一牛影视| 欧美在线观看网址综合| 亚洲欧洲精品一区二区三区不卡 | 在线播放精品| 亚洲国产一区视频| 亚洲精品麻豆| 国产欧美一区二区精品仙草咪| 久久精品九九| 欧美国产精品久久| 亚洲欧美日韩一区二区在线 | 韩国成人精品a∨在线观看| 欧美黄色片免费观看| 欧美网站在线| 看欧美日韩国产| 欧美性猛交xxxx免费看久久久| 久久久精品日韩| 欧美精品一区三区| 麻豆精品网站| 国产精品成人免费精品自在线观看| 久久久久久久精| 欧美三级特黄| 欧美黄网免费在线观看| 国产精品永久免费| 亚洲精品视频在线看| 伊人蜜桃色噜噜激情综合| 亚洲人成绝费网站色www| 国产一区二区三区久久久| 日韩视频一区二区三区在线播放| 国内一区二区三区在线视频| 一本色道久久88综合亚洲精品ⅰ | 亚洲伦理在线免费看| 激情亚洲一区二区三区四区| 一区二区三区国产精品| 亚洲精品小视频在线观看| 欧美一二区视频| 亚洲一区二区精品在线| 欧美激情第9页| 欧美激情1区2区3区| 国产自产精品| 先锋影音一区二区三区| 亚洲影院免费观看| 欧美日韩一区二区免费视频| 亚洲成色999久久网站| 在线观看日韩av先锋影音电影院| 性欧美videos另类喷潮| 性色av香蕉一区二区| 欧美性久久久| 亚洲视频在线观看网站| 一区二区免费在线播放| 欧美了一区在线观看| 亚洲人成精品久久久久| 99re亚洲国产精品| 欧美另类一区| 亚洲片在线资源| 99视频精品在线| 欧美理论电影网| 日韩亚洲欧美在线观看| 亚洲午夜在线观看视频在线| 欧美日韩国产麻豆| 在线亚洲成人| 欧美一区激情| 狠久久av成人天堂| 久久另类ts人妖一区二区| 看欧美日韩国产| 欧美日韩在线精品| 99综合视频| 久久福利一区| 国产一区二区三区免费不卡| 久久久精品五月天| 亚洲福利专区| 亚洲午夜黄色| 国产亚洲精品一区二555| 久久久亚洲精品一区二区三区| 欧美a级片一区| 一本色道88久久加勒比精品| 欧美午夜一区二区福利视频| 午夜精品婷婷| 欧美激情亚洲激情| 亚洲男人的天堂在线| 国产一区二区三区四区老人| 麻豆91精品91久久久的内涵| 日韩视频三区| 裸体女人亚洲精品一区| 亚洲精一区二区三区| 国产精品亚洲一区二区三区在线| 久久激情视频| 夜夜爽www精品| 米奇777超碰欧美日韩亚洲| 日韩亚洲欧美一区二区三区| 国产精品免费小视频| 美女免费视频一区| 亚洲在线视频一区| 亚洲国产高清aⅴ视频| 欧美一区2区三区4区公司二百| 亚洲国产美女久久久久| 国产精品夜色7777狼人| 欧美大片一区二区| 午夜精品国产更新| 亚洲裸体俱乐部裸体舞表演av| 久久激情五月激情| 亚洲午夜视频| 亚洲美女免费视频| 狠狠入ady亚洲精品| 国产精品高清在线| 欧美激情一区二区三区蜜桃视频| 亚洲欧美日韩在线一区| 夜色激情一区二区| 91久久精品美女| 久久伊伊香蕉| 欧美一区国产一区| 亚洲女同同性videoxma| 亚洲精品在线一区二区| 在线成人av.com| 国产日产精品一区二区三区四区的观看方式| 欧美成人视屏| 老司机精品视频网站| 欧美一区二区三区另类| 亚洲淫性视频| 亚洲一区3d动漫同人无遮挡| 亚洲三级免费电影| 亚洲国产精品久久精品怡红院| 久久夜精品va视频免费观看| 久久精品首页| 久久精选视频| 久久国产精品亚洲va麻豆| 亚洲欧美日韩精品久久久久| 夜夜嗨av一区二区三区网站四季av| 亚洲国产视频直播| 亚洲成人在线免费| 亚洲第一伊人| 亚洲国产精品一区在线观看不卡 | 欧美四级剧情无删版影片| 欧美日韩www| 欧美日韩黄色大片| 欧美日韩成人一区二区三区| 欧美国产精品日韩| 欧美日韩一区二区精品| 欧美日韩中文字幕在线| 国产精品进线69影院| 性娇小13――14欧美| 亚洲欧美日本精品| 国产在线拍揄自揄视频不卡99| 红桃视频欧美| 亚洲精品免费看| 在线亚洲免费| 欧美在线日韩| 看片网站欧美日韩| 亚洲电影成人| 亚洲伦理中文字幕| 亚洲欧美中文日韩在线| 久久免费视频观看| 欧美不卡视频一区| 国产精品大片wwwwww| 国产亚洲精品aa午夜观看| 亚洲动漫精品| 亚洲视频第一页| 久久久久久高潮国产精品视| 欧美ed2k| 亚洲特级片在线| 久久精品视频va| 欧美日韩国产在线观看| 国产精品自拍三区| 亚洲日韩视频| 久久超碰97人人做人人爱| 欧美国产日韩一区二区在线观看| 亚洲免费高清| 欧美在线免费观看视频| 欧美激情综合在线| 国产专区综合网| 中文在线一区| 欧美**人妖| 亚洲淫性视频| 欧美另类一区二区三区| 国产亚洲一区在线| 一区二区三区回区在观看免费视频| 久久久久国色av免费看影院 | 久久久国产91| 亚洲精品一区在线观看| 亚洲欧美国产精品专区久久| 免费观看成人www动漫视频| 国产精品一区二区你懂得 | 亚洲福利一区| 欧美一区网站| 亚洲欧洲久久| 久久久最新网址| 国产一区二区三区在线观看免费视频 | 国产精品视频xxx| 亚洲欧洲在线一区| 狂野欧美一区| 欧美一区中文字幕| 国产精品卡一卡二卡三| 一本色道久久综合精品竹菊|