COM入門第一部分 - 什么是COM和如何使用COM
作者:Michael Dunn
譯者:蔣國(guó)綱
本文目的
此文為剛開始學(xué)習(xí)COM并需要一些幫助來認(rèn)識(shí)其基礎(chǔ)的程序員而寫,文章簡(jiǎn)要地覆蓋了COM的規(guī)范,解釋一些COM的術(shù)語和怎樣重復(fù)使用存在的COM組件,但本文并不覆蓋創(chuàng)建一個(gè)COM的內(nèi)容。(譯者:關(guān)于如何創(chuàng)建一個(gè)COM會(huì)在《COM入門第二部分》有講解)
導(dǎo)言
COM(Component Object Model,組件對(duì)象模型)是個(gè)流行的“三字母縮寫詞”,它存在于Windows世界的所有角落,現(xiàn)在每天都有基于COM的巨量的新技術(shù)誕生。本文從最開始介紹COM,描述其潛在機(jī)制,向你展示如何使用COM組件,讀完本文,你將可以使用Windows內(nèi)置的和第三方提供的COM組件。本文假定你精通C++,我在例子中使用了一些MFC和ATL,但我會(huì)徹底講解它們,所以就算你不懂MFC和ATL,你可以讀懂,文章段落安排如下:
COM - 它到底是什么?一個(gè)對(duì)COM標(biāo)準(zhǔn)的快速介紹,使用COM并不需要懂得這個(gè),但我建議你還是看看以便更好理解;
基本元素的定義 - 講述COM的一些術(shù)語;
使用COM - 創(chuàng)建、使用和銷毀COM對(duì)象的概覽;
基本接口 - IUnknown,解釋這個(gè)基本接口的方法;
注意事項(xiàng) - 字符串處理,怎樣處理COM代碼中的字符串;
范例 - 用代碼演示本文所講述的內(nèi)容;
返回結(jié)果(HRESULT)處理 - HRESULT的描述,怎樣根據(jù)它來判斷正確和錯(cuò)誤;
參考書 - 如果你的雇主需要,你得在這方面多花費(fèi)一些。:)
COM - 它到底是什么?
COM,簡(jiǎn)單地說,是一種不同應(yīng)用程序和不同語言來共享二進(jìn)制代碼的方法,不同于C++,只是源代碼級(jí)的重用。Windows允許你使用DLL實(shí)現(xiàn)二進(jìn)制級(jí)的代碼共享,如kernel32.dll,user32.dll等,但因?yàn)檫@都是用C寫的DLL,所以它們只能被C或者理解C調(diào)用方式的語言所調(diào)用。MFC引入了另一種二進(jìn)制級(jí)的代碼共享機(jī)制--MFC extension DLLs,但這種機(jī)制限制更多,你只能在MFC程序中使用它們。而COM通過建立一種二進(jìn)制的規(guī)范來解決這些問題,這也意味著COM二進(jìn)制模塊要按照一種特別的結(jié)構(gòu)來組織,在內(nèi)存中亦然。規(guī)則是語言無關(guān)的,重?fù)?dān)交給了編譯器。(^o^)COM對(duì)象在內(nèi)存中的組織結(jié)構(gòu)和C++的虛函數(shù)一樣,這就是為什么大多數(shù)COM代碼都使用C++的原因,但記住,COM確實(shí)是語言無關(guān)的,因?yàn)樯傻慕Y(jié)果代碼可以被其它所有語言所使用。順便說,COM不是Win32規(guī)范,理論上,它能移植到Unix和其它任意的操作系統(tǒng),但我沒見過Windows世界以外的COM。(譯者:COM是微軟的核心技術(shù)之一,Office,DirectX,.net,到處都是COM,可見微軟熱衷于這項(xiàng)技術(shù)。)
基本元素定義
讓我們從最基礎(chǔ)的開始。
接口(interface,譯者:有些地方把Interface譯作“界面”,我認(rèn)為不妥,因?yàn)槿菀鬃屓艘詾槭?/span>“用戶界面”)就是一組函數(shù),這些函數(shù)稱為方法,借口名稱帶“I”字母前綴,例如IShellLink。C++中,接口類只包含純虛函數(shù)。接口可以繼承于其它接口,和C++普通類的繼承類似,但不允許多重繼承。
CoClass(Component Object Class的縮寫,譯者:可以翻譯成“COM類”,但效果不佳,所以保留英文不作翻譯)包含在一個(gè)DLL或者一個(gè)EXE中,其代碼隱藏在一個(gè)或多個(gè)接口背后,CoClass是接口的實(shí)現(xiàn),COM對(duì)象是CoClass內(nèi)存中的實(shí)例,注意,CoClass不等于C++的Class,雖然我們經(jīng)常用C++ Class來實(shí)現(xiàn)一個(gè)CoClass。COM Server(譯者:服務(wù)器,但翻譯為“服務(wù)器”似乎容易引起誤會(huì),以為是一臺(tái)電腦或者某服務(wù)器程序,所以我打算不譯,后面所提及到的Server,一律指COM Server,同理,Client就是COM客戶端),是個(gè)包含一個(gè)或多個(gè)CoClass的二進(jìn)制文件(DLL或者EXE)。
譯者:一個(gè)COM Server(可以是dll或者exe)可以包含多個(gè)CoClass,而一個(gè)CoClass可以具有多個(gè)接口,一個(gè)接口可以具有多個(gè)方法。(20071105)
注冊(cè)是創(chuàng)建注冊(cè)表?xiàng)l目來告訴Windows一個(gè)COM Server在什么地方的過程。反注冊(cè)則相反,移除注冊(cè)的條目。
GUID (讀音和“fluid”類似,代表全局單一標(biāo)識(shí)) 是一個(gè)128位數(shù)字。它作為COM語言無關(guān)性的一個(gè)標(biāo)識(shí),每個(gè)接口和CoClass都有GUID,因?yàn)镚UID是全球唯一,重名可以完全避免(如果你用API去創(chuàng)建它的話),有時(shí)你會(huì)遇到UUID(Universally Unique Identifier),作用和GUID一樣的。
Class ID,或者稱CLSID是CoClass的GUID,Interface ID,或者稱IID,是一個(gè)接口的GUID。
GUID在COM中使用如此廣泛有兩個(gè)理由:1、GUID僅僅是個(gè)數(shù)字,任何語言都支持;2、GUID是不會(huì)重復(fù)的,發(fā)行方便。
HRESULT是COM返回錯(cuò)誤代碼的完整類型,它不是個(gè)句柄(Handle),雖然它有個(gè)“H”前綴,稍后我會(huì)講如何利用它來檢查執(zhí)行情況。
最后,COM運(yùn)行庫(譯者:即COM Library,我認(rèn)為翻譯作“COM運(yùn)行庫”比翻譯作“COM庫”更合適)是操作系統(tǒng)與你交互的一部分,當(dāng)你在做COM相關(guān)的工作時(shí)。通常,COM運(yùn)行庫又被稱為“COM”,但我并不這樣,我怕會(huì)混淆。
使用COM
每種語言都有它處理對(duì)象的辦法,例如,C++在棧中建立它們,或動(dòng)態(tài)新建分配它們,由于COM必須語言無關(guān),COM運(yùn)行庫提供了它獨(dú)有的對(duì)象管理機(jī)制,下面用C++和它作一下比較:
1、建立一個(gè)新的對(duì)象
C++:使用new運(yùn)算符或者在棧中把對(duì)象建立起來。
COM:調(diào)用API或者COM運(yùn)行庫。
2、刪除一個(gè)對(duì)象
C++:使用delete運(yùn)算符或者讓這個(gè)棧中建立的對(duì)象超出有效范圍后自動(dòng)銷毀。
COM:所有的對(duì)象都有它們的引用計(jì)數(shù),當(dāng)調(diào)用工作完成時(shí)候,調(diào)用者必須告訴對(duì)象,對(duì)象減少引用計(jì)數(shù),當(dāng)引用計(jì)數(shù)變?yōu)?的時(shí)候,對(duì)象銷毀自己。
在創(chuàng)建和銷毀這個(gè)對(duì)象之間,你可以使用這個(gè)對(duì)象。當(dāng)你建立一個(gè)COM對(duì)象時(shí)候,你告訴COM運(yùn)行庫,你需要怎樣的接口,當(dāng)對(duì)象創(chuàng)建成功后,COM運(yùn)行庫返回一個(gè)指向你需要的接口的指針,你可以通過這個(gè)指針來調(diào)用各種方法,就像它指向的是一個(gè)規(guī)則的C++對(duì)象。
你可以調(diào)用CoCreateInstance()這個(gè)API來創(chuàng)建一個(gè)COM對(duì)象,CoCreateInstance原形如下:
HRESULT CoCreateInstance (
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID* ppv );
參數(shù)說明:
rclsid - 就是CoClass的CLSID了,例如你可以傳遞CLSID_ShellLink來表示要?jiǎng)?chuàng)建一個(gè)用于創(chuàng)建快捷方式的COM對(duì)象;
pUnkOuter - 這個(gè)參數(shù)用于集合COM對(duì)象,是給存在的CoClass添加新方法的途徑,我們傳NULL過去表示我們不使用集合;
dwClsContext - 表示我們要使用的COM Server,本文中,我們將使用最簡(jiǎn)單的Server類型--“進(jìn)程內(nèi)DLL”(in-process DLL,譯者:所謂in-process意思是COM對(duì)象存在于調(diào)用它的進(jìn)程之中),所以我們傳遞CLSCTX_INPROC_SERVER。提示:你不能使用CLSCTX_ALL(ATL默認(rèn)),因?yàn)檫@樣將在Windows 95這種沒安裝DCOM的系統(tǒng)中失??;
riid - 你要返回的接口類型ID,例如,你可以傳遞IID_IShellLink表示取得一個(gè)IShellLink接口;
ppv - 接口指針的地址,COM運(yùn)行庫通過它返回程序需要的接口。
當(dāng)你調(diào)用CoCreateInstance(),它就在注冊(cè)表中尋找這個(gè)CLSID,獲知COM Server的位置,將其加載入內(nèi)存,然后建立COM對(duì)象。
這里有個(gè)例子,用CLSID_ShellLink去獲取一個(gè)IShellLink接口,指向相應(yīng)的COM對(duì)象:
HRESULT hr;
IShellLink* pISL;
hr = CoCreateInstance (CLSID_ShellLink, // (in)CoClass的CLSID
NULL, // (in)集合,這里不使用
CLSCTX_INPROC_SERVER, // (in)Server類型為“進(jìn)程內(nèi)Server”
IID_IShellLink, // (in)接口的IID
(void**) &pISL ); // (out)返回接口指針
if ( SUCCEEDED ( hr ) )
{
// 調(diào)用pISL接口的各種方法
}
else
{
// 建立COM實(shí)例失敗,hr保存了出錯(cuò)值
}
首先我們定義一個(gè)HRESULT來保存CoCreateInstance()的返回值,用“SUCCEEDED”宏檢查這個(gè)返回值,返回TRUE代表成功,返回FALSE代表失敗,也有一個(gè)對(duì)應(yīng)的宏“FAILED”來檢測(cè)是否失敗。
刪除COM對(duì)象
如前面所說的,你并不需要自己刪除COM對(duì)象,你只需要告訴它,你沒有再使用它就行了,每個(gè)COM對(duì)象都有這個(gè)IUnknown接口,這個(gè)接口有個(gè)Release()函數(shù),當(dāng)你不再需要使用這個(gè)COM對(duì)象的時(shí)候,調(diào)用這個(gè)函數(shù)。一旦調(diào)用了Release(),你就不能再使用這個(gè)接口了,因?yàn)镃OM對(duì)象可能已經(jīng)在內(nèi)存中被銷毀。
如果你的應(yīng)用程序使用大量不同的COM對(duì)象,使用完接口之后調(diào)用Release()是非常重要的,如果你不釋放接口,COM對(duì)象(包括包含在代碼中的DLL)還將駐留內(nèi)存,但對(duì)你的程序已經(jīng)沒有任何用處了。如果你的程序需要持續(xù)使用很長(zhǎng)時(shí)間,你應(yīng)該在程序空閑時(shí)候調(diào)用CoFreeUnusedLibraries()這個(gè)API,這個(gè)API將卸載沒有任何外部引用的COM服務(wù),以此減少應(yīng)用程序的內(nèi)存使用。
繼續(xù)上面這個(gè)例子,下面說明如何使用Release():
// 先根據(jù)上述把COM實(shí)例創(chuàng)建好,然后……
if ( SUCCEEDED ( hr ) )
{
// 調(diào)用pISL接口的各種方法
// 告訴COM對(duì)象,我們使用完了
pISL->Release();
}
IUnknown接口將在下節(jié)中詳細(xì)講解。
基本接口 - IUnknown
每個(gè)COM接口都從IUnknown繼承,Unknown這個(gè)名字有些誤導(dǎo)人,其實(shí)它并非“不懂”,它指的是如果你有一個(gè)COM對(duì)象的IUnknown指針,而你“不懂”這個(gè)COM對(duì)象究竟是什么,每個(gè)COM對(duì)象都實(shí)現(xiàn)了IUnknown接口。
IUnknown有三個(gè)方法:
1、AddRef() - 告訴COM對(duì)象增加其引用計(jì)數(shù),如果你獲取一個(gè)接口指針的副本,你應(yīng)該調(diào)用這個(gè)方法;
2、Release() - 告訴COM對(duì)象減少其引用計(jì)數(shù);
3、QueryInterface() - 從COM對(duì)象請(qǐng)求獲取一個(gè)接口指針,如果一個(gè)CoClass有一個(gè)以上的接口,你應(yīng)該使用這個(gè)方法。
我們已經(jīng)明確看到了Release()的調(diào)用,但QueryInterface()呢?當(dāng)你用CoCreateInstance()創(chuàng)建COM對(duì)象的時(shí)候,你直接取得接口的指針,但如果一個(gè)COM對(duì)象有一個(gè)以上的接口(IUnknown不算),你就得使用QueryInterface()去取得你想要的接口,QueryInterface()的原形是:
HRESULT IUnknown::QueryInterface(REFIID iid, void** ppv);
參數(shù):
1、iid - 你要獲取的接口的IID;
2、ppv - 指向接口地址的指針,如果QueryInterface()成功取得了接口的話。
我們繼續(xù)“Shell Link”的例子,如果你已經(jīng)有了一個(gè)指向IShellLink接口的指針,pISL,你可以通過以下代碼取得一個(gè)IPersistFile接口:
HRESULT hr;
IPersistFile* pIPF;
hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );
接下去你可以用SUCCEEDED宏來檢測(cè)QueryInterface()是否成功,如果成功,你就可以使用這個(gè)接口了,和別的接口沒什么兩樣。當(dāng)然,在你不再使用它的時(shí)候,調(diào)用pIPF->Release()來釋放它。
注意事項(xiàng):字符串處理
我們離開主題一會(huì)兒,來討論怎樣處理COM中的字符串,如果你熟悉UNICODE和ANSI,并知道如何轉(zhuǎn)換它們,你可以跳過這一節(jié),否則還是閱讀本節(jié)吧。
無論什么時(shí)候COM方法返回一個(gè)string,這個(gè)string都是UNICODE,UNICODE是一種字符編碼方案,其所有字符長(zhǎng)度都是兩字節(jié),如果你需要讓字符串更加容易管理,你可以將其轉(zhuǎn)換為TCHAR字符串。
TCHAR和_t前綴的函數(shù)(例如_tcscpy())是為了讓你用同樣的代碼處理Unicode和ANSI準(zhǔn)備的,大多數(shù)情況下,你都是使用ANSI字符串和ANSI Windows API,所以本文的剩余部分,簡(jiǎn)單起見,將用TCHAR來代替char。
當(dāng)你從COM返回了Unicode的字符串后,你可以通過以下途徑將其轉(zhuǎn)換為char字符串:
1、調(diào)用WideCharToMultiByte();
2、調(diào)用CRT函數(shù) wcstombs();
3、使用CString構(gòu)造函數(shù)或者運(yùn)算符(只有MFC有效);
4、使用ATL轉(zhuǎn)換宏。
WideCharToMultiByte()的原形是:
int WideCharToMultiByte (
UINT CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int cchWideChar,
LPSTR lpMultiByteStr,
int cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar );
(譯者:原文在此列舉了WideCharToMultiByte的參數(shù)說明,我認(rèn)為沒有必要,不如自己打開MSDN查一下,所以略過)
這里有個(gè)WideCharToMultiByte的例子,其實(shí)它的使用并不像它的參數(shù)眾多所顯示出的那么復(fù)雜:
//假設(shè)我們已經(jīng)有了一個(gè)UNICODE字符串wszSomeString……
char szANSIString [MAX_PATH];
WideCharToMultiByte ( CP_ACP, // ANSI code page
WC_COMPOSITECHECK, // Check for accented characters
wszSomeString, // Source Unicode string
-1, // -1 means string is zero-terminated
szANSIString, // Destination char string
sizeof(szANSIString), // Size of buffer
NULL, // No default character
NULL ); // Don't care about this flag
這么一調(diào)用之后,szANSIString就包含了ANSI版本的Unicode字符串。
wcstombs()比較簡(jiǎn)單,卻能取代WideCharToMultiByte(),達(dá)到轉(zhuǎn)換的效果,它的原形是:
size_t wcstombs (
char* mbstr,
const wchar_t* wcstr,
size_t count );
參數(shù)是:
mbstr:一個(gè)獲取結(jié)果的ANSI緩存;
wcstr:要轉(zhuǎn)換的UNICODE;
count:mbstr的長(zhǎng)度。
其實(shí)wcstombs() 是使用了 WC_COMPOSITECHECK | WC_SEPCHARS 標(biāo)志來調(diào)用 WideCharToMultiByte()的,上面的例子使用wcstombs就變成了:
wcstombs(szANSIString, wszSomeString, sizeof(szANSIString));
MFC的CString類包括了接收UNICODE字符串的構(gòu)造函數(shù)和賦值運(yùn)算符,所以你可以利用CString來實(shí)現(xiàn)轉(zhuǎn)換功能,例如:
// 假設(shè)我們已經(jīng)有wszSomeString...
CString str1 ( wszSomeString ); // Convert with a constructor.
CString str2;
str2 = wszSomeString; // Convert with an assignment operator.
ATL macros,ATL存在轉(zhuǎn)換處理功能的宏,將UNICODE轉(zhuǎn)換為ANSI,就使用W2A()宏,實(shí)際上為了更準(zhǔn)確,使用OLE2A()宏更多些,“OLE”表示字符串來自COM或OLE源,這里有個(gè)例子:
#include <atlconv.h>
// 再次假設(shè)我們已經(jīng)有了wszSomeString...
{
char szANSIString [MAX_PATH];
USES_CONVERSION; // Declare local variable used by the macros.
lstrcpy ( szANSIString, OLE2A(wszSomeString) );
}
OLE2A()宏“返回”一個(gè)指向轉(zhuǎn)換好字符串的指針,但這個(gè)轉(zhuǎn)換好的字符串是存放在臨時(shí)的棧中變量,所以我們得用lstrcpy來給它做一個(gè)副本,其它你要關(guān)心的宏還有W2T()(Unicode轉(zhuǎn)換為TCHAR),還有W2CT()(Unicode轉(zhuǎn)換為const TCHAR)。
你可以一直保持用Unicode如果沒什么特別的要求,如果你要寫一個(gè)控制臺(tái)應(yīng)用程序,你可以用std::wcout來打印Unicode字符串,例如:
wcout<<wszSomeString;
但注意,wcout期望所有串中的字符是Unicode,因此,如果你有“常規(guī)”字符,你還是用std::cout來輸出它吧,如果你有字符串常量,那么用"L"前綴來使得它們成為Unicode字符串,例如:wcout<<L"The Oracle says..."<<endl<<wszOracleResponse;
使用Unicode有兩點(diǎn)限制:
1、你必須使用wcsXXX()字符串函數(shù)來操作它,比如wcslen();
2、在某些很少出現(xiàn)的情況下,你不可以將一個(gè)Unicode字符串傳遞給Windows 95的Windows API,為了使得代碼在Windows 95和Windows NT中一致,請(qǐng)使用TCHAR類型,它在MSDN中有講述。(譯者:Windows 95不支持Unicode,但現(xiàn)在誰還在用Windows 95?。浚?o:p>
用范例來總結(jié)
下面兩個(gè)例子將展示本文中所提及的COM的概念:
使用COM對(duì)象的單接口
第一個(gè)例子向你展示怎樣使用一個(gè)COM對(duì)象的單接口,這是你所遇到的最簡(jiǎn)單的例子了。代碼使用了包含在shell中Active Desktop的CoClass來獲取當(dāng)前桌面墻紙的文件名,你需要安裝Active Desktop來讓代碼正常工作。
步驟如下:
1、初始化COM運(yùn)行庫;
2、建立一個(gè)和Active Desktop交互的COM對(duì)象,取得IActiveDesktop接口;
3、調(diào)用COM對(duì)象的GetWallpaper方法;
4、如果GetWallpaper()調(diào)用成功,那么打印墻紙的文件名;
5、釋放接口;
6、釋放COM運(yùn)行庫。
WCHAR wszWallpaper [MAX_PATH];
CString strPath;
HRESULT hr;
IActiveDesktop* pIAD;
// 初始化COM運(yùn)行庫(讓W(xué)indows加載一些DLL文件),通常你要在執(zhí)行其它操作前執(zhí)行這一步,
// 在MFC程序中,用AfxOleInit()來替代之,在InitInstance()或者其它啟動(dòng)函數(shù)中調(diào)用
CoInitialize ( NULL );
//創(chuàng)建COM對(duì)象
hr = CoCreateInstance ( CLSID_ActiveDesktop,
NULL,
CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,
(void**) &pIAD );
if ( SUCCEEDED(hr) )
{
//如果成功創(chuàng)建COM對(duì)象,我們調(diào)用GetWallpaper()方法
hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );
if ( SUCCEEDED(hr) )
{
// 如果成功,打印它返回的文件名
// 注意,我在使用wcout來顯示wszWallpaper這個(gè)UNICODE字符串
// wcout是UNICODE版的cout
wcout << L"Wallpaper path is:/n" << wszWallpaper << endl << endl;
}
else
{
cout << _T("GetWallpaper() failed.") << endl << endl;
}
// 釋放接口
pIAD->Release();
}
else
{
cout << _T("CoCreateInstance() failed.") << endl << endl;
}
// 釋放COM運(yùn)行庫,如果是MFC程序,它會(huì)自動(dòng)釋放,無需手動(dòng)調(diào)用
CoUninitialize();
這個(gè)例子中,我使用std::wcout來顯示Unicode字符串wszWallpaper。
使用COM對(duì)象的多接口
第二個(gè)例子向你展示怎樣使用QueryInterface()來暴露COM對(duì)象的接口,代碼使用了shell中的Shell Link coclass來建立一個(gè)指向上個(gè)例子中我們獲取的墻紙文件的快捷方式。
步驟如下:
1、初始化COM運(yùn)行庫;
2、建立一個(gè)用來建立快捷方式的COM對(duì)象,并取得IShellLink接口;
3、調(diào)用IShellLink接口的SetPath()方法;
4、調(diào)用COM對(duì)象的QueryInterface()方法來獲得IPersistFile接口;
5、調(diào)用IPersistFile接口的Save()方法;
6、釋放接口;
7、釋放COM運(yùn)行庫。
CString sWallpaper = wszWallpaper; // wszWallpaper是前面獲取的墻紙文件路徑
IShellLink* pISL;
IPersistFile* pIPF;
// 初始化COM運(yùn)行庫(讓W(xué)indows加載一些DLL文件),通常你要在執(zhí)行其它操作前執(zhí)行這一步,
// 在MFC程序中,用AfxOleInit()來替代之,在InitInstance()或者其它啟動(dòng)函數(shù)中調(diào)用
CoInitialize ( NULL );
// 建一個(gè)COM對(duì)象,使用Shell提供的“Shell Link”CoClass
// 四個(gè)參數(shù)告訴COM我們需要怎樣的COM接口
hr = CoCreateInstance ( CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void**) &pISL );
if ( SUCCEEDED(hr) )
{
// 設(shè)置為快捷方式指向的目標(biāo)為墻紙文件
hr = pISL->SetPath ( sWallpaper );
if ( SUCCEEDED(hr) )
{
// 從COM對(duì)象取得第二個(gè)接口--IPersistFile
hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );
if ( SUCCEEDED(hr) )
{
// 調(diào)用Save()方法將快捷方式保存到文件,注意該函數(shù)第一個(gè)參數(shù)是UNICODE字符串
hr = pIPF->Save ( L"C://wallpaper.lnk", FALSE );
// 釋放IPersistFile接口
pIPF->Release();
}
}
// 釋放IShellLink接口
pISL->Release();
}
// 這里省略了出錯(cuò)處理代碼,讀者自己完成
// 釋放COM運(yùn)行庫,在MFC應(yīng)用程序中,就不需要這樣手動(dòng)釋放,MFC會(huì)自動(dòng)完成釋放
CoUninitialize();
結(jié)果處理
我已經(jīng)在上面的例子中作了簡(jiǎn)單的出錯(cuò)處理,使用SUCCEEDED和FAILED宏,現(xiàn)在我來給出更詳細(xì)的處理。
HRESULT是一個(gè)32位有符號(hào)整型,以非負(fù)代表成功,負(fù)數(shù)代表失敗,HRESULT有3個(gè)段:標(biāo)志段(成功或者失敗的標(biāo)志),設(shè)備代碼段和狀態(tài)段,設(shè)備代碼段表示HRESULT來自哪個(gè)組件或者程序,微軟給它每個(gè)組件分配不同的設(shè)備代碼,比如COM有一類代碼,Task Scheduler有一類代碼,等等,這個(gè)代碼是16位長(zhǎng)度,它沒有確定的意義,就好像GetLastError()返回的值。
如果你在winerror.h文件中查閱錯(cuò)誤代碼,你將看到很多HRESULT的列表,大概是“設(shè)備代碼段代號(hào)_標(biāo)志段代號(hào)_描述”這樣的常量格式,通常,這些HRESULT可以被任何組件返回。如:E_OUTOFMEMORY,它沒有設(shè)備代碼段;REGDB_E_READREGDB:設(shè)備代碼段 = REGDB, 指的是注冊(cè)表數(shù)據(jù)庫方面,E = error,READREGDB是錯(cuò)誤描述(不能讀取數(shù)據(jù)庫);S_OK:設(shè)備代碼段 = 普通;S = 成功,OK是描述,一切正常!
HRESULT列表很多很多,幸運(yùn)的是我們有簡(jiǎn)單的辦法來檢測(cè)HRESULT的意思,而不需要查閱winerror.h文件,內(nèi)建的HRESULT可以通過一個(gè)叫“Error Lookup”的工具來查詢其意義,比如你在CoCreateInstance()前忘了調(diào)用CoInitialize(),那么CoCreateInstance()就返回代碼0x800401F0,你可以將它輸入到Error Lookup中去,并得知其描述:“CoInitialize沒有被調(diào)用”。
還有種辦法,你可以通過debuger來查看HRESULT描述,如果你有個(gè)叫hres的HRESULT,你可以在Watch window中輸入“hres,hr”作為值來觀察,“,hr”告訴VC顯示這個(gè)HRESULT值的描述。
參考書
《Essential COM》,作者:Don Box,ISBN 0-201-63446-5,此書內(nèi)容是關(guān)于COM的規(guī)范及IDL(Interface Definition Language),書的前兩章詳細(xì)講述了COM的規(guī)范和它是為了解決哪些問題而設(shè)計(jì)的。
《MFC Internals》,作者:George Shepherd和Scot Wingo,ISBN 0-201-40721-3,有深度地講述了MFC對(duì)COM的支持。
《Beginning ATL 3 COM Programming》,作者:Richard Grimes等,ISBN 1-861001-20-7,這本書非常有深度地講述了如何用ATL來編寫你的COM組件。
(第一部分完)
聯(lián)系客服