接触VC之四:COM组件模型基础
From:?http://daimajishu.iteye.com/blog/1081292
一年又一年,已經(jīng)又過了一年了。我VC的生涯已經(jīng)兩歲了。可以相當?shù)貞c賀一下喲。回顧這一年的學習(唉,還沒有工作實踐呢。這年頭,工作不好找哇。),還學了不少的好東西。其中,最重要的就是COM組件模型,我個人覺得這個幾乎是Windows的核心。許多先進的技術(比如微軟著名的DirectX,ADO,沒有人會不知道吧)都以COM組件的形式發(fā)布的。現(xiàn)在,我瞄上了另一個好東東,就是泛型編程技術。它能夠編寫出清晰、靈活、高度可重用的代碼,在ATL中就可以依稀看出它的影子(現(xiàn)在網(wǎng)上ATL文章有很多,我以后也會談到它)。好了,關于泛型編程的事今后再談。
按照我以前的計劃,我應該談談我對COM組件模型的認識了。一來可以對自己的學習狀況進行總結(jié)。二來,請教高手,可以幫忙指出錯漏之處。三來,說不定會對初學者們有所幫助。請各位高手多多指正啊.在這里先謝了。
?一、動態(tài)鏈接庫:
動態(tài)鏈態(tài)庫是大部分COM組件的承載對象(不要在意ocx,它同樣也是dll,只不過改了一下后綴而已)。當然Exe同樣也是可以的(TTS中的TextToSpeech對象就是一個例證),只不過在事實上要少得多。
在Windows初期,動態(tài)鏈態(tài)庫的出現(xiàn)是一場革命。它改變了Windows的一生,也為當今Windows操作系統(tǒng)的霸主地位打下一塊堅實的基石。(關于Windows的歷史問題,我一直沒有弄得太清楚。請VCKBASE的有關史學家們盡快寫出一篇文章來吧^_^)。
微軟對動態(tài)鏈接庫就是這樣解釋的:
動態(tài)鏈接庫 (DLL) 是作為共享函數(shù)庫的可執(zhí)行文件。動態(tài)鏈接提供了一種方法,使進程可以調(diào)用不屬于其可執(zhí)行代碼的函數(shù)。函數(shù)的可執(zhí)行代碼位于一個 DLL 中,該 DLL 包含一個或多個已被編譯、鏈接并與使用它們的進程分開存儲的函數(shù)。DLL 還有助于共享數(shù)據(jù)和資源。多個應用程序可同時訪問內(nèi)存中單個 DLL 副本的內(nèi)容。
嗯,講得很清楚。動態(tài)鏈接庫首先是一個可執(zhí)行文件(微軟解釋說,exe叫做起直接可執(zhí)行文件),它里面包含著一組需要共享的函數(shù)。當使用時,動態(tài)鏈接庫(和Windows系統(tǒng))會提供一個方法來使我們的應用程序可以調(diào)用其中的函數(shù)。此外,動態(tài)鏈接庫還會包含一些資源(如:圖標、對話框模板等等)。在MFC中,微軟在現(xiàn)有動態(tài)鏈接庫的基礎上施用了一些技巧來提供一些另外功能,如MFC類的導出。
動態(tài)鏈接庫的鏈接方式大致分為兩類: 靜態(tài)鏈接和動態(tài)鏈接.
靜態(tài)鏈接又叫隱式鏈接,這種鏈接方式使我們在代碼中不用語句來指示系統(tǒng)中,我們的應用程序要加載哪些動態(tài)鏈接庫。其靜態(tài)鏈接聲明是放在工程屬性中的(或者使用#pragma comment(lib,"XXX.lib"),這個可以和#include放在一起)。在指定時,只需要輸入其動態(tài)鏈接庫相應的導入庫文件(.lib)。然后,你就可以在程序的任何地方像調(diào)用普通函數(shù)一樣調(diào)用該動態(tài)鏈接庫中存在的函數(shù)了(當然,你需要包含其相應的頭文件。一般情況下,頭文件會和LIB文件一塊給出)。通過這種方法生成的程序在運行初始化的時候(具體到什么時候不太清楚。但我可以肯定是在WinMain函數(shù)之前了^_^),會自動將動態(tài)鏈接庫加載在系統(tǒng)環(huán)境中,并將其映射到我們應用程序的進程當中去。當我們調(diào)用一個我們進程沒有定義的函數(shù)時,VC運行庫會通過查找LIB文件的相關信息找到相應動態(tài)鏈接庫的函數(shù)并調(diào)用它。進程結(jié)束時,系統(tǒng)會缷載動態(tài)鏈接庫。
動態(tài)鏈接又叫顯式鏈接,顧名思義這種方式讓我們必需在代碼通過調(diào)用API來顯式地加載動態(tài)鏈接庫。COM組件模型全部都是采用這種方式來加載進程內(nèi)組件模塊(就是Dll)的。(我覺得微軟的專業(yè)術語有些混亂耶)。這個方式有許多好處,它可以在運行時決定具體要加載哪個鏈接庫,要調(diào)用哪個函數(shù)…這才叫動態(tài)鏈接呢。
要使用動態(tài)鏈接庫并不難,首先要調(diào)用LoadLibrary,其原型如下:
參數(shù)lpFileName是要加載的動態(tài)鏈接庫的文件名。如果加載成功的話,就返回其句柄。否則的,返回NULL。
與這個API相配對的是FreeLibrary,其原型如下:
這個就不用我多說了吧。
當動態(tài)鏈接庫被LoadLibrary所加載時,C運行庫通過_DllMainCRTStartup來完成動態(tài)鏈接庫的初始化,如全局對象(變量)、靜態(tài)成員變量的生成以及賦初值。最重要的是它還會調(diào)用DllMain函數(shù)。每一個動態(tài)鏈接庫都必須有這個函數(shù),就像應用程序必須有main或WinMain一樣。它的原型是:
你可以通過DllMain函數(shù)來完成你的動態(tài)鏈接庫中的環(huán)境初始化和析構(gòu)操作。啊,事情是這樣的:
DllMain被調(diào)用有四種情況,這四種情況可以從fdwReason參數(shù)來分別出來:
它們分別是
1. DLL_PROCESS_ATTACH,當動態(tài)鏈接庫被加載到進程時,調(diào)用DllMain。
2. DLL_THREAD_ATTACH,當進程建立一個新線程時,進程會調(diào)用所以已加載了的動態(tài)鏈接庫的DllMain。
3. DLL_THREAD_DETACH,當一個線程結(jié)束時,進程會調(diào)用所以已加載了的動態(tài)鏈接庫的DllMain。
4. DLL_THREAD_DETACH,當動態(tài)鏈接庫被缷載或進程結(jié)束時,調(diào)用DllMain。
這樣,通過DllMain函數(shù)就可以反應出一個動態(tài)鏈接庫的生命周期了。
當加載成功后,我們會得到一個HMODULE句柄。這個句柄的使用與HINSTANCE應用程序?qū)嵗木浔芟嗨?追查定義,HMODULE就是HINSTANCE)。我們可以使用下面一些API函數(shù)來使用HMODULE句柄:
LoadBitmap、LoadIcon、LoadString、…、GetProcAddress等等
其中,最重要的就是GetProcAddress。它是用來返回鏈接庫中的某個函數(shù)的函數(shù)指針,然后我們就可以通過這個函數(shù)指針來調(diào)用這個鏈接庫函數(shù)了。(如果你對函數(shù)指針不熟的話,最好再看一看C\C++語法書。我覺得函數(shù)指針的聲明方法很怪異)其原型如下:
啊,hModule我就不說了。lpProcName參數(shù)是一個字符串,這個字符串寫著我們要找的函數(shù)的函數(shù)名。如果找到了的話,就返回這個函數(shù)的指針,否則返回NULL。
舉個例子:
比如說有個鏈接庫函數(shù)是”int Plus(int nAugend, int nAddend)”,我要調(diào)用它。
如果我以及這個鏈接庫沒有問題的話,我想輸出結(jié)果應該是2。
我仍然認為函數(shù)指針的聲明很怪異,可讀性并不高,所以我一般會換一種寫法。
雖然會出一個警告,但我覺得這樣會舒服一些。
嗯,動態(tài)鏈接庫的情況就基本如此了。具體動態(tài)鏈接庫的編寫會和COM組件的編寫一塊在后續(xù)章節(jié)里談及。
?二、面向?qū)ο蟮慕M件模型-COM
Windows系統(tǒng)霸主地位詁計三四年內(nèi)是不會被動搖的。因此,有n多Windows開發(fā)平臺出現(xiàn)在我們面前。n多種開發(fā)語言是百花齊放啊。于是,我們像圣經(jīng)里說的那樣,操著不同的語言,彼此無法溝通。為改變這一現(xiàn)實,可愛的比爾就站出來了,”偶要改變世界!”。微軟公司制定一個基于二進制通用接口規(guī)范-Component Object Model(組件對象模型)。但是,一開始COM的解決目標并非是為了通用接口,而是應用于復合文檔(OLE)的實現(xiàn)。而今由于語言無關性、進程透明性、可重用性、保密性(除非高手高手高高手,有誰能從匯編碼中看出實現(xiàn)技術來)、而且編寫并不困難,所以發(fā)展成為了一項應用廣泛的技術。
1) 組件對象與接口
組件對象、接口是COM的根基。
下面,請允許我用C++對象做一個類比。
組件對象與C++對象的意義是基本相同的。它是一個功能、屬性與邏輯的整體。它是一個實體對象,通過對它的接口操作,可以使用它所提供的功能。
接口相當于C++對象中的public成員。它被暴露給外部使用者,使用者只被允許調(diào)用這些被暴露在外面的接口來使用對象的功能。與public成員有所不同的是,接口不是一個變量也不是一個函數(shù),而應該是一組函數(shù)。在邏輯上,這個組函數(shù)應該是功能相關的。一個組件對象可以擁有許多個接口。
我只知道C++的COM實現(xiàn)方法,至于Dephi我就一無所知。
C++實現(xiàn)方法是:由C++類對象來完成組件對象的實現(xiàn),由C++純虛類來代表接口。C++類對象通過多重繼承多個接口,來的擁有多個接口。
下面,我舉一個例子,來說明C++中的組件對象與接口的關系(下面的例子并不是一個COM實現(xiàn),只是用來表示組件對象與接口的關系)
我如果要做一個人的組件對象的話,我首先要定義一些接口來表示人的外部表現(xiàn)行為。
我將人的行為分成了生理學、心理學和動力學三類,讓它們分別表示人不同的行為。那么,這么三組相關函數(shù)就是三個接口。C++組件對象的實現(xiàn)就是從這些接口中多重派生,并實現(xiàn)它們。這樣,我們就得到一個組件對象(聲明啊,本示例只是一個表示概念,真正的COM組件對象還需要加一些東東)。
class human : public physiology,public psychics,public dynamics { public:void eat(Food in) {cout << "Good! Very delicious!"; }void drink(Liquid in) {cout << "No! I am not drunk!"; }Something toilet() {cout << "hum…….";return dejecta(); }Sound laugh() {return Sound("Ha…Ha…Ha"); }Sound cry() {return Sound("dad!Don’t beat my buns."); }Sound angry() {return Sound("where did you go last night? Darling."); }Speed run() {cout << "Run, Police come!";return 20km/h; }Speed walk() {cout << "out. yegg, I am no…not afraid o….of y…you.";return 1m/s; }Interval jump() {cout << "Yeah….";return 4m; } };
這樣,一個組件對象就定義完了。當使用組件對象時,系統(tǒng)所給予你的一個指針。它是一個組件對象實現(xiàn)了的虛類指針,我們可以使用它來調(diào)用組件對象對于這個純虛類所實現(xiàn)的功能(當然,我們有選擇什么虛類指針的權利;只要組件對象支持就可以了)。
總之,一個組件對象外部特征是由不同的接口也就是這些虛類所組成,它們向使用者展現(xiàn)組件所提供的功能。
注:如果你的C++虛函數(shù)沒學得不太好的話,那么請找一本C++語法書再看一看. 或請參看VCKBASE第12期的《解析動態(tài)聯(lián)編》。
2) 標識符(GUID)
上面,我說過COM組件是基于二進制的。那么要我們使用簽名(比如說類名、接口名)來指定一個組件顯然是不理想的了(至少在識別方面會有些麻煩)。那么,既然是二進制系統(tǒng)最方便當然就是使用數(shù)字標識了。于是,微軟定義了這么一個結(jié)構(gòu)標準:
結(jié)構(gòu)用來儲存一些數(shù)字信息,來表識一個COM對象,接口以及其它COM元素。這個結(jié)構(gòu)體就叫做標識符。
在C++中一個標識符是這么表示的:
同樣的標識符在其它非C環(huán)境中是這么表示的:
{54bf6567-1007-11d1-b0aa-444553540000} 這個標識符代表著一個COM對象,這是因為一個COM對象的標識符名都以CLISID_為前綴。接口名則是以IID_為前綴。不要問我,標識符定義與對象具體有什么關系式。我不知道。它們根本就沒有什么關系的。一個COM對象在編寫時,我們會使用隨機的方法來確定它的標識符(這個工作可以由VC來幫我們搞定)。一旦COM對象得到一個標識符并發(fā)布出去的話,那么就不能更改了。另外,不要擔心GUID會有所沖突。如果你的高中數(shù)學已經(jīng)及格了的話,那么請算一算128位二進制中,重復的概率會有多少。假如你真的發(fā)現(xiàn)了GUID有沖突的話(你要保證這不是人為),建議你趕去買彩票吧。你離500萬不遠了。
3) IUnknown接口
COM模式所有接口必須遵守一定規(guī)范,這就是IUnknown接口的出處。每個一接口都必須從這個接口繼承。在C++中,微軟已經(jīng)為我們把IUnknown定義好了:
注:void *可以指向任何對象。我開始的時候?qū)oid*一點都不理解。這里使用的原因是傳出與傳入指針類型不確定。
QueryInterface函數(shù)功能是當我們得到一個接口指針,并且我們想得到另一個接口指針的時候,提供幫助。我們將我們想要得到的接口的標識符傳給iid,將把指針的做一個次&來傳給ppv。如果QueryInterface成功的話,會返回S_OK。我們指針中就會指向我們想要的接口。
AddRef,Release用于實現(xiàn)引用計數(shù)機制。
在二進制系統(tǒng)中,組件對象不像C++環(huán)境中對象那樣具有明確的生存期。可能會出現(xiàn)這種情況,兩個(或者兩個以上)的地方(可能是不同的程序之間,也可能是不同的線程之間)同時使用著一個組件對象,如果其中一個地方delete掉了組件對象的話。其它地方不可能會知道,當它們嘗試調(diào)用這個象的話,輕則導致重傷,重則導致死亡。這不是我們希望看到的。于是,COM模型設制一個引用計數(shù)機制。
當一個地方開始使用對象的時候,它必須調(diào)用AddRef()一次。當我們使用QueryInterface時候,QueryInterface必須為我們調(diào)用一次AddRef()。AddRef()會使組件對象的引用計數(shù)增1。當這個地方不再使用對象時,它必須調(diào)用Release()一次。Release()會使組件對象的引用計數(shù)減1。當組件對象的引用計數(shù)變成0,就表明沒有人再去使用組件對象了。這時,組件對象應該結(jié)束自己的生命。這樣,就保證了組件對象生存期間其它程序的安全。
當然,你可以使用自己的引用機制,只要你的行為上支持AddRef和Release。比如說,不設置對象的引用計數(shù),而是為每個接口設置一個引用計數(shù)。當所有的接口引用計數(shù)都為0時,delete對象。
好了,前面的示例中,我并沒有遵守IUknown規(guī)范,下面我要遵守它。我把上次同樣東西用……省略掉了。
// {6AAF876E-FCED-4ee0-B5D3-63CD6E2242F5} static const GUID IID_IPhysiology = { 0x6aaf876e, 0xfced, 0x4ee0, { 0xb5, 0xd3, 0x63, 0xcd, 0x6e, 0x22, 0x42, 0xf5 } }; class IPhysiology:public IUnknown { public:…… };// {183FC7A1-4C27-4c38-B72D-D1326E2E8A7C} static const GUID IID_IPsychics = { 0x183fc7a1, 0x4c27, 0x4c38, { 0xb7, 0x2d, 0xd1, 0x32, 0x6e, 0x2e, 0x8a, 0x7c } }; class IPsychics:public IUnknown { public:…… };// {5F144D5C-A20C-42e7-8F91-4D5CAE430B29} static const GUID IID_IDynamics = { 0x5f144d5c, 0xa20c, 0x42e7, { 0x8f, 0x91, 0x4d, 0x5c, 0xae, 0x43, 0xb, 0x29 } }; class IDynamics:public IUnknown { public:…… };// {ABFA7022-7E2F-4d0e-8A4F-F58BBCEBB2DA} static const GUID CLISID_Human = { 0xabfa7022, 0x7e2f, 0x4d0e, { 0x8a, 0x4f, 0xf5, 0x8b, 0xbc, 0xeb, 0xb2, 0xda } }; class human : public IPhysiology,public IPsychics,public IDynamics { public: …… human() {m_ulRef = 0; }HRESULT QueryInterface(const IID& iid, void **ppv) {if (iid == IID_IUnknown || iid == IID_IPhysiology){*ppv = static_cast<IPhysiology*>(this);(IPhysiology*)(*this))->AddRef();}else if (iid == IID_IPsychics){*ppv = static_cast<IPsychics*>(this);(IPsychics*)(*this))->AddRef();}else if (iid == IID_IDynamics){*ppv = static_cast<IDynamics*>(this);(IDynamics*)(*this))->AddRef();}else{*ppv = NULL;return E_NOTINTERFACE;}return S_OK; }ULONG AddRef() {return ++m_ulRef; }ULONG Release() {m_ulRef--;if (m_ulRef <= 0){m_ulRef = 0;delete this;}return m_ulRef; }ULONG m_ulRef; };
這樣我們的組件對象就定義完全了。
下面給出我們這個組件對象的IDL描述和圖形描述
#include "olectl.h" import "oaidl.idl"; import "ocidl.idl";[object,uuid(6AAF876E-FCED-4ee0-B5D3-63CD6E2242F5),nonextensible,helpstring("IPhysiology 接口"),pointer_default(unique) ] interface IPhysiology : IUnknown { void eat(Food in);void drink(Liquid in);Somethings toilet(); };[object,uuid(5F144D5C-A20C-42e7-8F91-4D5CAE430B29),nonextensible,helpstring("IPsychics 接口"),pointer_default(unique) ] interface IPsychics : IUnknown { Sound laugh();Sound cry();Sound angry(); };[object,uuid(5F144D5C-A20C-42e7-8F91-4D5CAE430B29),nonextensible,helpstring("IDynamics 接口"),pointer_default(unique) ] interface IDynamics : IUnknown { Speed run() = 0;Speed walk() = 0;Interval jump() = 0; };[uuid(6CC7B329-B92F-4A8F-9CDD-1AB6D7E4CF4D),version(1.0),helpstring("OLEOBJECT 1.0 類型庫") ] library OLEOBJECTLib {importlib("stdole2.tlb");[uuid(62FD0E39-DA84-4B19-BAB0-960A27AC2B71),helpstring("OlePaint Class")]coclass OlePaint{[default] interface IPhysiology,interface IPsychics,interface IDynamics}; };
請伃細,觀察上面的描述IDL代碼和圖形。并不是太難吧。
4) COM對象的接口原則
為了規(guī)范COM的接口機制,微軟向COM開發(fā)者發(fā)布了COM對象的接口原則。
(1)IUnknown接口的等價性
當我們要等到兩個接口指針,我如何判斷它們從屬于一個對象呢。COM接口原則規(guī)定,同一個對象的Queryinterface的IID_IUnknown查詢出來的IUnknown指針值應當相等。也就是說,每個對象的IUnknown指是唯一的。我們可以通過判斷IUnknown指針是否相等來判斷它們是否指向同一個對象。
當然,如果查詢的不是IUnknown接口,則無此限制。同一對象對非IUnknown接口的查詢值可以不同。
(2)接口自反性,對一個接口來說,查詢它本身應該是允許的。
設pPsychics是已賦值IPsychics的接口。
那么pPsychics->QueryInterface(IID_IPsychics,(void **) &XXX);應當成功。
(3)接口對稱性,當我們從一個接口查詢到另一個接口時,那么我們再從結(jié)果接口還可以查詢到原來的接口。
例如:
如果pSrcPsychics->QueryInterface(IID_IDynamics,(void **) &pDynamics);成功的話。
那么pDynamics->QueryInterface(IID_IPsychics,(void **) &pTarget);也相當成功。
(4)接口傳遞性。如果我們從第一個接口查詢到了第二個接口,又從第二個接口查詢到了第三接口。則我們應該能夠從第三個接口查詢到第一個接口。其它依此類推。
(5)接口查詢時間無關性。當我們在某時查詢到一個接口,那么在任意時刻也應該查詢到這個接口。
嗯,COM的基本知識好像這么多了。好像片篇太長呵。那么COM實現(xiàn)方法留到下一篇吧。
(待續(xù)...)
作者信息:
釋雪?
MSN Messenger:Blue_Atlantis400@hotmail.com
QQ:63068279
總結(jié)
以上是生活随笔為你收集整理的接触VC之四:COM组件模型基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: X5开发中buttongrounp对应c
- 下一篇: Protues仿真 8X8 LED点阵