COM編程快速入門


COM編程快速入門

  • COM是一種跨應用和語言共享二進制代碼的方法。與C++不同,它提倡源代碼重用。ATL便是一個很好的例證。源碼級重用雖然好,但只能用於C++。它還帶來了名字沖突的可能性,更不用說不斷拷貝重用代碼而導致工程膨脹和臃腫
 

1.什么是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。

2.COM所包含的基本元素

接口可以從其它接口繼承,這里所說的繼承的原理就好像C++中的單繼承。接口是不允許多繼承的。

coclass(簡稱組件對象類——component object class)被包含在DLL或EXE中,並且包含着一個或者多個接口的代碼。組件 對象類(coclasss)實現這些接口。COM對象在內存中表現為組件對象類(coclasss)的一個實例。注意COM“類”和C++“類”是不相同的,盡管常常COM類實現的就是一個C++類。

COM服務器是包含了一個或多個coclass的二進制(DLL或EXE)。

注冊(Registration)是創建注冊表入口的一個過程,告訴Windows 操作系統COM服務器放在什么位置。取消注冊(Unregistration)則相反——從注冊表刪除這些注冊入口。

GUID(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本身。但是為了避免混淆才分開描述的

3.COM的基本接口----IUnknown

每一個COM接口都派生於IUnknown。

它的原意是如果有一個指向某COM對象的IUnknown指針,就不用知道潛在的對象是什么,因為每個COM對象都實現IUnknown。

IUnknown 有三個方法:

(1)AddRef() – 通知COM對象增加它的引用計數。如果你進行了一次接口指針的拷貝,就必須調用一次這個方法,並且原始的值和拷貝的值兩者都要用到。在本文的例子中沒有用到AddRef()方法。

(2)Release() – 通知COM對象減少它的引用計數。

(3)QueryInterface() – 從COM對象請求一個接口指針。當coclass實現一個以上的接口時,就要用到這個方法。

當你用CoCreateInstance()創建對象的時候,你得到一個返回的接口指針。如果這個COM對象實現一個以上的接口(不包括IUnknown),你就必須用QueryInterface()方法來獲得任何你需要的附加的接口指針。QueryInterface()的原型如下:

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

以下是參數解釋:

iid :所請求的接口的IID。

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

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

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

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

4.COM對象的使用和處理

每一種語言都有其自己處理對象的方式。例如,C++是在棧中創建對象,或者用new動態分配。因為COM必須獨立於語言,所以COM庫為自己提供對象管理例程。下面是對COM對象管理和C++對象管理所做的一個比較:

(1)創建一個新對象

C++中,用new操作符,或者在棧中創建對象。

COM中,調用COM庫中的API。

(2)刪除對象

C++中,用delete操作符,或將棧對象踢出。

COM中,所有的對象保持它們自己的引用計數。調用者必須通知 對象什么時候用完這個對象。當引用計數為零時,COM對象將自己從內存中釋放。

由此可見,對象處理的兩個階段:創建和銷毀,缺一不可。當創建COM對象時要通知COM庫使用哪一個接口。如果這個對象創建成功,COM庫返回所請求接口的指針。然后通過這個指針調用方法,就像使用常規C++對象指針一樣。

5.COM對象的創建和刪除

(1).COM對象的創建

為了創建COM對象並從這個對象獲得接口,須調用COM庫的API函 數,CoCreateInstance()。其原型如下:

1. 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接口指針。

01. HRESULT hr_COM;
02. IShellLink* pISL_COM;
03. hr_COM = CoCreateInstance ( CLSID_ShellLink, CLSID NULL,  CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)      &pISL );
04. if ( SUCCEEDED ( hr_COM ) )
05. {
06. // 用pISL_COM調用方法
07. }
08. else
09. {
10. // 不能創建COM對象,hr_COM 為出錯代碼
11. }

首先聲明一個接受CoCreateInstance()返回值的HRESULT和IShellLink指針。調用CoCreateInstance()來創建新的COM對象。 如果hr接受到一個表示成功的代碼,則SUCCEEDED宏返回TRUE,否則返回FALSE。FAILED是一個與 SUCCEEDED對應的宏用來檢 查失敗代碼。

(2)COM對象的刪除

前面說過,你不用釋放COM對象,只要告訴它們你已經用完對象。IUnknown是每一個COM對象必須實現的接口,它有一個方法 ,Release()。調用這個方法通知COM對象你不再需要對象。一旦調用了這個方法之后,就不能再次使用這個接口,因為這個 COM對象可能從此就從內存中消失了。

如果你的應用程序使用許多不同的COM對象,因此在用完某個接口后調用Release()就顯得非常重要。如果你不釋放接口,這 個COM對象(包含代碼的 DLLs)將保留在內存中,這會增加不必要的開銷。如果你的應用程序要長時間運行,就應該在應用 程序處於空閑期間調用 CoFreeUnusedLibraries() API。這個API將卸載任何沒有明顯引用的COM服務器,因此這也降低了應 用程序使用的內存開銷。

繼續用上面的例子來說明如何使用Release():

// 像上面一樣創建COM 對象后

1. if ( SUCCEEDED ( hr ) )
2. {
3. pISL_COM->Release();
4. }

6.在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 串轉換宏。

7.COM例子

a:單接口COM對象

01. WCHAR wszWallpaper [MAX_PATH];
02. CString strPath;
03. HRESULT hr;
04. IActiveDesktop* pIAD;
05. CoInitialize ( NULL );
06. hr = CoCreateInstance ( CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER, IID_IActiveDesktop, (void**) &pIAD );
07. if ( SUCCEEDED(hr) )
08. {
09. hr = pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );
10. if ( SUCCEEDED(hr) )
11. {
12. wcout << L"Wallpaper path is:/n " << wszWallpaper << endl << endl;
13. }
14. else
15. {
16. cout << _T("GetWallpaper() failed.") << endl << endl; } pIAD->Release();
17. }
18. else
19. {
20. cout << _T("CoCreateInstance() failed.") << endl << endl;
21. }
22.
23. CoUninitialize();

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

b:雙接口COM對象

01. CString sWallpaper = wszWallpaper;
02. ANSI IShellLink* pISL;
03. IPersistFile* pIPF;
04. CoInitialize ( NULL );
05. hr = CoCreateInstance ( CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &pISL );
06. if ( SUCCEEDED(hr) )
07. {
08. hr = pISL->SetPath ( sWallpaper );
09. if ( SUCCEEDED(hr) )
10. {
11. hr = pISL->QueryInterface ( IID_IPersistFile, (void**) &pIPF );
12. if ( SUCCEEDED(hr) )
13. {
14. hr = pIPF->Save ( L"C://wallpaper.lnk", FALSE );
15. pIPF->Release();
16. }
17. }
18. pISL->Release();
19. }
20. CoUninitialize();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM