1) COM組件有三個最基本的接口類,分別是IUnknown,、IClassFactory,、IDispatch。 COM規(guī)范規(guī)定任何組件,、任何接口都必須從IUnknown繼承,,IUnknown包含三個函數(shù),分別是 QueryInterface,、AddRef,、Release,。這三個函數(shù)是無比重要的,而且它們的排列順序也是不可改變的,。QueryInterface用于查詢組件實現(xiàn)的其它接口,,說白了也就是看看這個組件的父類中還有哪些接口類,AddRef用于增加引用計數(shù),,Release用于減少引用計數(shù),。引用計數(shù)也是COM中的一個非常重要的概念。大體上簡單的說來可以這么理解,,COM組件是個DLL,,當客戶程序要用它時就要把它裝到內(nèi)存里。另一方面,,一個組件也不是只給你一個人用的,,可能會有很多個程序同時都要用到它。但實際上DLL只裝載了一次,,即內(nèi)存中只有一個COM組件,,那COM組件由誰來釋放?由客戶程序嗎,?不可能,,因為如果你釋放了組件,那別人怎么用,,所以只能由COM組件自己來負責,。所以出現(xiàn)了引用計數(shù)的概念,COM維持一個計數(shù),,記錄當前有多少人在用它,,每多一次調(diào)用計數(shù)就加一,少一個客戶用它就減一,,當最后一個客戶釋放它的時侯,,COM知道已經(jīng)沒有人用它了,它的使用已經(jīng)結(jié)束了,,那它就把它自己給釋放了,。引用計數(shù)是COM編程里非常容易出錯的一個地方,但所幸VC的各種各樣的類庫里已經(jīng)基本上把AddRef的調(diào)用給隱含了,,在我的印象里,,我編程的時侯還從來沒有調(diào)用過AddRef,我們只需在適當?shù)臅r侯調(diào)用Release,。至少有兩個時侯要記住調(diào)用Release,,第一個是調(diào)用了 QueryInterface以后,第二個是調(diào)用了任何得到一個接口的指針的函數(shù)以后,,記住多查MSDN 以確定某個函數(shù)內(nèi)部是否調(diào)用了AddRef,,如果是的話那調(diào)用Release的責任就要歸你了,。 IUnknown的這三個函數(shù)的實現(xiàn)非常規(guī)范但也非常煩瑣,容易出錯,,所幸的事我們可能永遠也不需要自己來實現(xiàn)它們,。 IClassFactory的作用是創(chuàng)建COM組件。我們已經(jīng)知道COM組件實際上就是一個類,,那我們平常是怎么實例化一個類對象的?是用‘new’命令!很簡單吧,,COM組件也一樣如此,。但是誰來new它呢?不可能是客戶程序,,因為客戶程序不可能知道組件的類名字,,如果客戶知道組件的類名字那組件的可重用性就要打個大大的折扣了,事實上客戶程序只不過知道一個代表著組件的128位的數(shù)字串而已,,這個等會再介紹,。所以客戶無法自己創(chuàng)建組件,而且考慮一下,,如果組件是在遠程的機器上,,你還能new出一個對象嗎?所以創(chuàng)建組件的責任交給了一個單獨的對象,,這個對象就是類廠,。每個組件都必須有一個與之相關(guān)的類廠,這個類廠知道怎么樣創(chuàng)建組件,,當客戶請求一個組件對象的實例時,,實際上這個請求交給了類廠,由類廠創(chuàng)建組件實例,,然后把實例指針交給客戶程序,。這個過程在跨進程及遠程創(chuàng)建組件時特別有用,因為這時就不是一個簡單的new操作就可以的了,,它必須要經(jīng)過調(diào)度,,而這些復雜的操作都交給類廠對象去做了。IClassFactory最重要的一個函數(shù)就是CreateInstance,,顧名思議就是創(chuàng)建組件實例,,一般情況下我們不會直接調(diào)用它,API函數(shù)都為我們封裝好它了,,只有某些特殊情況下才會由我們自己來調(diào)用它,,這也是VC編寫COM組件的好處,使我們有了更多的控制機會,,而VB給我們這樣的機會則是太少太少了,。 IDispatch叫做調(diào)度接口,。它的作用何在呢?這個世上除了C++還有很多別的語言,,比如VB,、 VJ、VBScript,、JavaScript等等,。可以這么說,,如果這世上沒有這么多亂七八糟的語言,,那就不會有IDispatch。:-) 我們知道COM組件是C++類,,是靠虛函數(shù)表來調(diào)用函數(shù)的,,對于VC來說毫無問題,這本來就是針對C++而設(shè)計的,,以前VB不行,,現(xiàn)在VB也可以用指針了,也可以通過VTable來調(diào)用函數(shù)了,,VJ也可以,,但還是有些語言不行,那就是腳本語言,,典型的如 VBScript,、JavaScript。不行的原因在于它們并不支持指針,,連指針都不能用還怎么用多態(tài)性啊,,還怎么調(diào)這些虛函數(shù)啊。唉,,沒辦法,,也不能置這些腳本語言于不顧吧,現(xiàn)在網(wǎng)頁上用的都是這些腳本語言,,而分布式應(yīng)用也是COM組件的一個主要市場,,它不得不被這些腳本語言所調(diào)用,既然虛函數(shù)表的方式行不通,,我們只能另尋他法了,。時勢造英雄,IDispatch應(yīng)運而生,。:-) 調(diào)度接口把每一個函數(shù)每一個屬性都編上號,,客戶程序要調(diào)用這些函數(shù)屬性的時侯就把這些編號傳給IDispatch接口就行了,IDispatch再根據(jù)這些編號調(diào)用相應(yīng)的函數(shù),,僅此而已,。當然實際的過程遠比這復雜,,僅給一個編號就能讓別人知道怎么調(diào)用一個函數(shù)那不是天方夜潭嗎,你總得讓別人知道你要調(diào)用的函數(shù)要帶什么參數(shù),,參數(shù)類型什么以及返回什么東西吧,,而要以一種統(tǒng)一的方式來處理這些問題是件很頭疼的事。IDispatch接口的主要函數(shù)是Invoke,,客戶程序都調(diào)用它,,然后Invoke再調(diào)用相應(yīng)的函數(shù),如果看一看MS的類庫里實現(xiàn) Invoke的代碼就會驚嘆它實現(xiàn)的復雜了,,因為你必須考慮各種參數(shù)類型的情況,,所幸我們不需要自己來做這件事,而且可能永遠也沒這樣的機會,。:-) (2) dispinterface接口,、Dual接口以及Custom接口 這一小節(jié)放在這里似乎不太合適,,因為這是在ATL編程時用到的術(shù)語,。我在這里主要是想談一下自動化接口的好處及缺點,用這三個術(shù)語來解釋可能會更好一些,,而且以后遲早會遇上它們,,我將以一種通俗的方式來解釋它們,可能并非那么精確,,就好象用偽代碼來描述算法一樣,。-:) 所謂的自動化接口就是用IDispatch實現(xiàn)的接口。我們已經(jīng)講解過IDispatch的作用了,,它的好處就是腳本語言象VBScript,、 JavaScript也能用COM組件了,從而基本上做到了與語言無關(guān)它的缺點主要有兩個,,第一個就是速度慢效率低,。這是顯而易見的,通過虛函數(shù)表一下子就可以調(diào)用函數(shù)了,,而通過Invoke則等于中間轉(zhuǎn)了道手續(xù),,尤其是需要把函數(shù)參數(shù)轉(zhuǎn)換成一種規(guī)范的格式才去調(diào)用函數(shù),耽誤了很多時間,。所以一般若非是迫不得已我們都想用VTable的方式調(diào)用函數(shù)以獲得高效率,。第二個缺點就是只能使用規(guī)定好的所謂的自動化數(shù)據(jù)類型。如果不用IDispatch我們可以想用什么數(shù)據(jù)類型就用什么類型,,VC會自動給我們生成相應(yīng)的調(diào)度代碼,。而用自動化接口就不行了,因為Invoke的實現(xiàn)代碼是VC事先寫好的,,而它不能事先預料到我們要用到的所有類型,,它只能根據(jù)一些常用的數(shù)據(jù)類型來寫它的處理代碼,,而且它也要考慮不同語言之間的數(shù)據(jù)類型轉(zhuǎn)換問題。所以VC自動化接口生成的調(diào)度代碼只適用于它所規(guī)定好的那些數(shù)據(jù)類型,,當然這些數(shù)據(jù)類型已經(jīng)足夠豐富了,,但不能滿足自定義數(shù)據(jù)結(jié)構(gòu)的要求。你也可以自己寫調(diào)度代碼來處理你的自定義數(shù)據(jù)結(jié)構(gòu),,但這并不是一件容易的事,。考慮到IDispatch的種種缺點(它還有一個缺點,,就是使用麻煩,,:-) )現(xiàn)在一般都推薦寫雙接口組件,稱為dual接口,,實際上就是從IDispatch繼承的接口,。我們知道任何接口都必須從 IUnknown繼承,IDispatch接口也不例外,。那從IDispatch繼承的接口實際上就等于有兩個基類,,一個是IUnknown,一個是IDispatch,,所以它可以以兩種方式來調(diào)用組件,,可以通過 IUnknown用虛函數(shù)表的方式調(diào)用接口方法,也可以通過IDispatch::Invoke自動化調(diào)度來調(diào)用,。這就有了很大的靈活性,,這個組件既可以用于C++的環(huán)境也可以用于腳本語言中,同時滿足了各方面的需要,。 相對比的,,dispinterface是一種純粹的自動化接口,可以簡單的就把它看作是IDispatch接口 (雖然它實際上不是的),,這種接口就只能通過自動化的方式來調(diào)用,,COM組件的事件一般都用的是這種形式的接口。 Custom接口就是從IUnknown接口派生的類,,顯然它就只能用虛函數(shù)表的方式來調(diào)用接口了 (3) COM組件有三種,,進程內(nèi)、本地,、遠程,。對于后兩者情況必須調(diào)度接口指針及函數(shù)參數(shù)。 COM是一個DLL,,它有三種運行模式,。它可以是進程內(nèi)的,即和調(diào)用者在同一個進程內(nèi),也可以和調(diào)用者在同一個機器上但在不同的進程內(nèi),,還可以根本就和調(diào)用者在兩臺機器上,。這里有一個根本點需要牢記,就是COM組件它只是一個DLL,,它自己是運行不起來的,,必須有一個進程象父親般照顧它才行,即COM組件必須在一個進程內(nèi).那誰充當看護人的責任呢,?先說說調(diào)度的問題,。調(diào)度是個復雜的問題,以我的知識還講不清楚這個問題,,我只是一般性的談?wù)剮讉€最基本的概念,。我們知道對于WIN32程序,每個進程都擁有4GB的虛擬地址空間,,每個進程都有其各自的編址,,同一個數(shù)據(jù)塊在不同的進程里的編址很可能就是不一樣的,所以存在著進程間的地址轉(zhuǎn)換問題,。這就是調(diào)度問題,。對于本地和遠程進程來說,DLL 和客戶程序在不同的編址空間,,所以要傳遞接口指針到客戶程序必須要經(jīng)過調(diào)度,。Windows 已經(jīng)提供了現(xiàn)成的調(diào)度函數(shù),,就不需要我們自己來做這個復雜的事情了,。對遠程組件來說函數(shù)的參數(shù)傳遞是另外一種調(diào)度。DCOM是以RPC為基礎(chǔ)的,,要在網(wǎng)絡(luò)間傳遞數(shù)據(jù)必須遵守標準的網(wǎng)上數(shù)據(jù)傳輸協(xié)議,,數(shù)據(jù)傳遞前要先打包,傳遞到目的地后要解包,,這個過程就是調(diào)度,,這個過程很復雜,不過Windows已經(jīng)把一切都給我們做好了,,一般情況下我們不需要自己來編寫調(diào)度DLL,。 我們剛說過一個COM組件必須在一個進程內(nèi)。對于本地模式的組件一般是以EXE的形式出現(xiàn),,所以它本身就已經(jīng)是一個進程,。對于遠程DLL,我們必須找一個進程,,這個進程必須包含了調(diào)度代碼以實現(xiàn)基本的調(diào)度,。這個進程就是dllhost.exe。這是COM默認的DLL代理。實際上在分布式應(yīng)用中,,我們應(yīng)該用MTS來作為DLL代理,,因為MTS有著很強大的功能,是專門的用于管理分布式DLL組件的工具,。 調(diào)度離我們很近又似乎很遠,,我們編程時很少關(guān)注到它,這也是COM的一個優(yōu)點之一,,既平臺無關(guān)性,,無論你是遠程的、本地的還是進程內(nèi)的,,編程是一樣的,,一切細節(jié)都由COM自己處理好了,所以我們也不用深究這個問題,,只要有個概念就可以了,,當然如果你對調(diào)度有自己特殊的要求就需要深入了解調(diào)度的整個過程了,這里推薦一本《COM+技術(shù)內(nèi)幕》,,這絕對是一本講調(diào)度的好書,。 (4) COM組件的核心是IDL。 我們希望軟件是一塊塊拼裝出來的,,但不可能是沒有規(guī)定的胡亂拼接,,總是要遵守一定的標準,各個模塊之間如何才能親密無間的合作,,必須要事先共同制訂好它們之間交互的規(guī)范,,這個規(guī)范就是接口。我們知道接口實際上都是純虛類,,它里面定義好了很多的純虛函數(shù),,等著某個組件去實現(xiàn)它,這個接口就是兩個完全不相關(guān)的模塊能夠組合在一起的關(guān)鍵試想一下如果我們是一個應(yīng)用軟件廠商,,我們的軟件中需要用到某個模塊,,我們沒有時間自己開發(fā),所以我們想到市場上找一找看有沒有這樣的模塊,,我們怎么去找呢,?也許我們需要的這個模塊在業(yè)界已經(jīng)有了標準,已經(jīng)有人制訂好了標準的接口,,有很多組件工具廠商已經(jīng)在自己的組件中實現(xiàn)了這個接口,,那我們尋找的目標就是這些已經(jīng)實現(xiàn)了接口的組件,我們不關(guān)心組件從哪來,,它有什么其它的功能,,我們只關(guān)心它是否很好的實現(xiàn)了我們制訂好的接口。這種接口可能是業(yè)界的標準,也可能只是你和幾個廠商之間內(nèi)部制訂的協(xié)議,,但總之它是一個標準,,是你的軟件和別人的模塊能夠組合在一起的基礎(chǔ),是COM組件通信的標準,。 COM具有語言無關(guān)性,,它可以用任何語言編寫,也可以在任何語言平臺上被調(diào)用,。但至今為止我們一直是以C++的環(huán)境中談COM,,那它的語言無關(guān)性是怎么體現(xiàn)出來的呢?或者換句話說,,我們怎樣才能以語言無關(guān)的方式來定義接口呢,?前面我們是直接用純虛類的方式定義的,但顯然是不行的,,除了C++誰還認它呢,?正是出于這種考慮,微軟決定采用IDL來定義接口,。說白了,,IDL實際上就是一種大家都認識的語言,用它來定義接口,,不論放到哪個語言平臺上都認識它,。我們可以想象一下理想的標準的組件模式,我們總是從IDL開始,,先用IDL制訂好各個接口,,然后把實現(xiàn)接口的任務(wù)分配不同的人,有的人可能善長用VC,,有的人可能善長用VB,,這沒關(guān)系,,作為項目負責人我不關(guān)心這些,,我只關(guān)心你把最終的DLL 拿給我。這是一種多么好的開發(fā)模式,,可以用任何語言來開發(fā),,也可以用任何語言來欣賞你的開發(fā)成果。 (5) COM組件的運行機制,,即COM是怎么跑起來的,。 這部分我們將構(gòu)造一個創(chuàng)建COM組件的最小框架結(jié)構(gòu),然后看一看其內(nèi)部處理流程是怎樣的 IUnknown *pUnk=NULL; 這就是一個典型的創(chuàng)建COM組件的框架,,不過我的興趣在CoCreateInstance身上,,讓我們來看看它內(nèi)部做了一些什么事情。以下是它內(nèi)部實現(xiàn)的一個偽代碼: CoCreateInstance(....) 這段話的意思就是先得到類廠對象,再通過類廠創(chuàng)建組件從而得到IUnknown指針,。繼續(xù)深入一步,,看看CoGetClassObject的內(nèi)部偽碼: CoGetClassObject(.....) (6) 一個典型的自注冊的COM DLL所必有的四個函數(shù) DllGetClassObject:用于獲得類廠指針 (7) 注冊表在COM中的重要作用 首先要知道GUID的概念,,COM中所有的類、接口,、類型庫都用GUID來唯一標識,,GUID是一個128位的字串,根據(jù)特制算法生成的GUID可以保證是全世界唯一的,。 COM組件的創(chuàng)建,,查詢接口都是通過注冊表進行的。有了注冊表,,應(yīng)用程序就不需要知道組件的DLL文件名,、位置,只需要根據(jù)CLSID查就可以了,。當版本升級的時侯,,只要改一下注冊表信息就可以神不知鬼不覺的轉(zhuǎn)到新版本的DLL。
|
|
來自: liujian6916 > 《COM/COM 》