




已閱讀5頁,還剩5頁未讀, 繼續免費閱讀
版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
用VC進行COM編程所必須掌握的理論知識作者:unknown 更新時間: 2005-05-07這篇文章是給初學者看的,盡量寫得比較通俗易懂,并且盡量避免編程細節。完全是根據我自己的學習體會寫的,其中若有技術上的錯誤之處,請大家多多指正。 一、為什么要用COM 軟件工程發展到今天,從一開始的結構化編程,到面向對象編程,再到現在的COM編程,目標只有一個,就是希望軟件能象積方塊一樣是累起來的,是組裝起來的,而不是一點點編出來的。結構化編程是函數塊的形式,通過把一個軟件劃分成許多模塊,每個模塊完成各自不同的功能,盡量做到高內聚低藕合,這已經是一個很好的開始,我們可以把不同的模塊分給不同的人去做,然后合到一塊,這已經有了組裝的概念了。軟件工程的核心就是要模塊化,最理想的情況就是100%內聚0%藕合。整個軟件的發展也都是朝著這個方向走的。結構化編程方式只是一個開始。下一步就出現了面向對象編程,它相對于面向功能的結構化方式是一個巨大的進步。我們知道整個自然界都是由各種各樣不同的事物組成的,事物之間存在著復雜的千絲萬縷的關系,而正是靠著事物之間的聯系、交互作用,我們的世界才是有生命力的才是活動的。我們可以認為在自然界中事物做為一個概念,它是穩定的不變的,而事物之間的聯系是多變的、運動的。事物應該是這個世界的本質所在。面向對象的著眼點就是事物,就是這種穩定的概念。每個事物都有其固有的屬性,都有其固有的行為,這些都是事物本身所固有的東西,而面向對象的方法就是描述出這種穩定的東西。而面向功能的模塊化方法它的著眼點是事物之間的聯系,它眼中看不到事物的概念它只注重功能,我們平常在劃分模塊的時侯有沒有想過這個函數與哪些對象有關呢?很少有人這么想,一個函數它實現一種功能,這個功能必定與某些事物想聯系,我們沒有去掌握事物本身而只考慮事物之間是怎么相互作用而完成一個功能的。說白了,這叫本末倒置,也叫急功近利,因為不是我們智慧不夠,只是因為我們沒有多想一步。面向功能的結構化方法因為它注意的只是事物之間的聯系,而聯系是多變的,事物本身可能不會發生大的變化,而聯系則是很有可能發生改變的,聯系一變,那就是另一個世界了,那就是另一種功能了。如果我們用面向對象的方法,我們就可以以不變應萬變,只要事先把事物用類描述好,我們要改變的只是把這些類聯系起來的方法,只是重新使用我們的類庫,而面向過程的方法因為它構造的是一個不穩定的世界,所以一點小小的變化也可能導致整個系統都要改變。然而面向對象方法仍然有問題,問題在于重用的方法。搭積木式的軟件構造方法的基礎是有許許多多各種各樣的可重用的部件、模塊。我們首先想到的是類庫,因為我們用面向對象的方法產生的直接結果就是許多的類。但類庫的重用是基于源碼的方式,這是它的重大缺陷。首先它限制了編程語言,你的類庫總是用一種語言寫的吧,那你就不能拿到別的語言里用了。其次你每次都必須重新編譯,只有編譯了才能與你自己的代碼結合在一起生成可執行文件。在開發時這倒沒什么,關鍵在于開發完成后,你的EXE都已經生成好了,如果這時侯你的類庫提供廠商告訴你他們又做好了一個新的類庫,功能更強大速度更快,而你為之心動又想把這新版的類庫用到你自己的程序中,那你就必須重新編譯、重新調試!這離我們理想的積木式軟件構造方法還有一定差距,在我們的設想里希望把一個模塊拿出來再換一個新的模塊是非常方便的事,可是現在不但要重新編譯,還要冒著很大的風險,因為你可能要重新改變你自己的代碼。另一種重用方式很自然地就想到了是DLL的方式。Windows里到處是DLL,它是Windows 的基礎,但DLL也有它自己的缺點。總結一下它至少有四點不足。(1)函數重名問題。DLL里是一個一個的函數,我們通過函數名來調用函數,那如果兩個DLL里有重名的函數怎么辦?(2)各編譯器對C函數的名稱修飾不兼容問題。對于C函數,編譯器要根據函數的參數信息為它生成修飾名,DLL庫里存的就是這個修飾名,但是不同的編譯器產生修飾的方法不一樣,所以你在VC 里編寫的DLL在BC里就可以用不了。不過也可以用extern C;來強調使用標準的C函數特性,關閉修飾功能,但這樣也喪失了C的重載多態性功能。(3)路徑問題。放在自己的目錄下面,別人的程序就找不到,放在系統目錄下,就可能有重名的問題。而真正的組件應該可以放在任何地方甚至可以不在本機,用戶根本不需考慮這個問題。(4)DLL與EXE的依賴問題。我們一般都是用隱式連接的方式,就是編程的時侯指明用什么DLL,這種方式很簡單,它在編譯時就把EXE與DLL綁在一起了。如果DLL發行了一個新版本,我們很有必要重新鏈接一次,因為DLL里面函數的地址可能已經發生了改變。DLL的缺點就是COM的優點。首先我們要先把握住一點,COM和DLL一樣都是基于二進制的代碼重用,所以它不存在類庫重用時的問題。另一個關鍵點是,COM本身也是DLL,既使是ActiveX控件.ocx它實際上也是DLL,所以說DLL在還是有重用上有很大的優勢,只不過我們通過制訂復雜的COM協議,通COM本身的機制改變了重用的方法,以一種新的方法來利用DLL,來克服DLL本身所固有的缺陷,從而實現更高一級的重用方法。COM沒有重名問題,因為根本不是通過函數名來調用函數,而是通過虛函數表,自然也不會有函數名修飾的問題。路徑問題也不復存在,因為是通過查注冊表來找組件的,放在什么地方都可以,即使在別的機器上也可以。也不用考慮和EXE的依賴關系了,它們二者之間是松散的結合在一起,可以輕松的換上組件的一個新版本,而應用程序混然不覺。 二、用VC進行COM編程,必須要掌握哪些COM理論知識 我見過很多人學COM,看完一本書后覺得對COM的原理比較了解了,COM也不過如此,可是就是不知道該怎么編程序,我自己也有這種情況,我也是經歷了這樣的階段走過來的。要學COM的基本原理,我推薦的書是COM技術內幕。但僅看這樣的書是遠遠不夠的,我們最終的目的是要學會怎么用COM去編程序,而不是拼命的研究COM本身的機制。所以我個人覺得對COM的基本原理不需要花大量的時間去追根問底,沒有必要,是吃力不討好的事。其實我們只需要掌握幾個關鍵概念就夠了。這里我列出了一些我自己認為是用VC編程所必需掌握的幾個關鍵概念。(這里所說的均是用C+語言條件下的COM編程方式) (1) COM組件實際上是一個C+類,而接口都是純虛類。組件從接口派生而來。我們可以簡單的用純粹的C+的語法形式來描述COM是個什么東西: class IObject public: virtual Function1(.) = 0; virtual Function2(.) = 0; . ; class MyObject : public IObject public: virtual Function1(.). virtual Function2(.). . ; 看清楚了嗎?IObject就是我們常說的接口,MyObject就是所謂的COM組件。切記切記接口都是純虛類,它所包含的函數都是純虛函數,而且它沒有成員變量。而COM組件就是從這些純虛類繼承下來的派生類,它實現了這些虛函數,僅此而已。從上面也可以看出,COM組件是以 C+為基礎的,特別重要的是虛函數和多態性的概念,COM中所有函數都是虛函數,都必須通過虛函數表VTable來調用,這一點是無比重要的,必需時刻牢記在心。 (2) COM組件有三個最基本的接口類,分別是IUnknown、IClassFactory、IDispatch。 COM規范規定任何組件、任何接口都必須從IUnknown繼承,IUnknown包含三個函數,分別是 QueryInterface、AddRef、Release。這三個函數是無比重要的,而且它們的排列順序也是不可改變的。QueryInterface用于查詢組件實現的其它接口,說白了也就是看看這個組件的父類中還有哪些接口類,AddRef用于增加引用計數,Release用于減少引用計數。引用計數也是COM中的一個非常重要的概念。大體上簡單的說來可以這么理解,COM組件是個DLL,當客戶程序要用它時就要把它裝到內存里。另一方面,一個組件也不是只給你一個人用的,可能會有很多個程序同時都要用到它。但實際上DLL只裝載了一次,即內存中只有一個COM組件,那COM組件由誰來釋放?由客戶程序嗎?不可能,因為如果你釋放了組件,那別人怎么用,所以只能由COM組件自己來負責。所以出現了引用計數的概念,COM維持一個計數,記錄當前有多少人在用它,每多一次調用計數就加一,少一個客戶用它就減一,當最后一個客戶釋放它的時侯,COM知道已經沒有人用它了,它的使用已經結束了,那它就把它自己給釋放了。引用計數是COM編程里非常容易出錯的一個地方,但所幸VC的各種各樣的類庫里已經基本上把AddRef的調用給隱含了,在我的印象里,我編程的時侯還從來沒有調用過AddRef,我們只需在適當的時侯調用Release。至少有兩個時侯要記住調用Release,第一個是調用了 QueryInterface以后,第二個是調用了任何得到一個接口的指針的函數以后,記住多查MSDN 以確定某個函數內部是否調用了AddRef,如果是的話那調用Release的責任就要歸你了。 IUnknown的這三個函數的實現非常規范但也非常煩瑣,容易出錯,所幸的事我們可能永遠也不需要自己來實現它們。 IClassFactory的作用是創建COM組件。我們已經知道COM組件實際上就是一個類,那我們平常是怎么實例化一個類對象的?是用new命令!很簡單吧,COM組件也一樣如此。但是誰來new它呢?不可能是客戶程序,因為客戶程序不可能知道組件的類名字,如果客戶知道組件的類名字那組件的可重用性就要打個大大的折扣了,事實上客戶程序只不過知道一個代表著組件的128位的數字串而已,這個等會再介紹。所以客戶無法自己創建組件,而且考慮一下,如果組件是在遠程的機器上,你還能new出一個對象嗎?所以創建組件的責任交給了一個單獨的對象,這個對象就是類廠。每個組件都必須有一個與之相關的類廠,這個類廠知道怎么樣創建組件,當客戶請求一個組件對象的實例時,實際上這個請求交給了類廠,由類廠創建組件實例,然后把實例指針交給客戶程序。這個過程在跨進程及遠程創建組件時特別有用,因為這時就不是一個簡單的new操作就可以的了,它必須要經過調度,而這些復雜的操作都交給類廠對象去做了。IClassFactory最重要的一個函數就是CreateInstance,顧名思議就是創建組件實例,一般情況下我們不會直接調用它,API函數都為我們封裝好它了,只有某些特殊情況下才會由我們自己來調用它,這也是VC編寫COM組件的好處,使我們有了更多的控制機會,而VB給我們這樣的機會則是太少太少了。 IDispatch叫做調度接口。它的作用何在呢?這個世上除了C+還有很多別的語言,比如VB、 VJ、VBScript、JavaScript等等。可以這么說,如果這世上沒有這么多亂七八糟的語言,那就不會有IDispatch。:-) 我們知道COM組件是C+類,是靠虛函數表來調用函數的,對于VC來說毫無問題,這本來就是針對C+而設計的,以前VB不行,現在VB也可以用指針了,也可以通過VTable來調用函數了,VJ也可以,但還是有些語言不行,那就是腳本語言,典型的如 VBScript、JavaScript。不行的原因在于它們并不支持指針,連指針都不能用還怎么用多態性啊,還怎么調這些虛函數啊。唉,沒辦法,也不能置這些腳本語言于不顧吧,現在網頁上用的都是這些腳本語言,而分布式應用也是COM組件的一個主要市場,它不得不被這些腳本語言所調用,既然虛函數表的方式行不通,我們只能另尋他法了。時勢造英雄,IDispatch應運而生。:-) 調度接口把每一個函數每一個屬性都編上號,客戶程序要調用這些函數屬性的時侯就把這些編號傳給IDispatch接口就行了,IDispatch再根據這些編號調用相應的函數,僅此而已。當然實際的過程遠比這復雜,僅給一個編號就能讓別人知道怎么調用一個函數那不是天方夜潭嗎,你總得讓別人知道你要調用的函數要帶什么參數,參數類型什么以及返回什么東西吧,而要以一種統一的方式來處理這些問題是件很頭疼的事。IDispatch接口的主要函數是Invoke,客戶程序都調用它,然后Invoke再調用相應的函數,如果看一看MS的類庫里實現 Invoke的代碼就會驚嘆它實現的復雜了,因為你必須考慮各種參數類型的情況,所幸我們不需要自己來做這件事,而且可能永遠也沒這樣的機會。:-) (3) dispinterface接口、Dual接口以及Custom接口 這一小節放在這里似乎不太合適,因為這是在ATL編程時用到的術語。我在這里主要是想談一下自動化接口的好處及缺點,用這三個術語來解釋可能會更好一些,而且以后遲早會遇上它們,我將以一種通俗的方式來解釋它們,可能并非那么精確,就好象用偽代碼來描述算法一樣。-:) 所謂的自動化接口就是用IDispatch實現的接口。我們已經講解過IDispatch的作用了,它的好處就是腳本語言象VBScript、 JavaScript也能用COM組件了,從而基本上做到了與語言無關它的缺點主要有兩個,第一個就是速度慢效率低。這是顯而易見的,通過虛函數表一下子就可以調用函數了,而通過Invoke則等于中間轉了道手續,尤其是需要把函數參數轉換成一種規范的格式才去調用函數,耽誤了很多時間。所以一般若非是迫不得已我們都想用VTable的方式調用函數以獲得高效率。第二個缺點就是只能使用規定好的所謂的自動化數據類型。如果不用IDispatch我們可以想用什么數據類型就用什么類型,VC會自動給我們生成相應的調度代碼。而用自動化接口就不行了,因為Invoke的實現代碼是VC事先寫好的,而它不能事先預料到我們要用到的所有類型,它只能根據一些常用的數據類型來寫它的處理代碼,而且它也要考慮不同語言之間的數據類型轉換問題。所以VC自動化接口生成的調度代碼只適用于它所規定好的那些數據類型,當然這些數據類型已經足夠豐富了,但不能滿足自定義數據結構的要求。你也可以自己寫調度代碼來處理你的自定義數據結構,但這并不是一件容易的事。考慮到IDispatch的種種缺點(它還有一個缺點,就是使用麻煩,:-) )現在一般都推薦寫雙接口組件,稱為dual接口,實際上就是從IDispatch繼承的接口。我們知道任何接口都必須從 IUnknown繼承,IDispatch接口也不例外。那從IDispatch繼承的接口實際上就等于有兩個基類,一個是IUnknown,一個是IDispatch,所以它可以以兩種方式來調用組件,可以通過 IUnknown用虛函數表的方式調用接口方法,也可以通過IDispatch:Invoke自動化調度來調用。這就有了很大的靈活性,這個組件既可以用于C+的環境也可以用于腳本語言中,同時滿足了各方面的需要。 相對比的,dispinterface是一種純粹的自動化接口,可以簡單的就把它看作是IDispatch接口 (雖然它實際上不是的),這種接口就只能通過自動化的方式來調用,COM組件的事件一般都用的是這種形式的接口。 Custom接口就是從IUnknown接口派生的類,顯然它就只能用虛函數表的方式來調用接口了 (4) COM組件有三種,進程內、本地、遠程。對于后兩者情況必須調度接口指針及函數參數。 COM是一個DLL,它有三種運行模式。它可以是進程內的,即和調用者在同一個進程內,也可以和調用者在同一個機器上但在不同的進程內,還可以根本就和調用者在兩臺機器上。這里有一個根本點需要牢記,就是COM組件它只是一個DLL,它自己是運行不起來的,必須有一個進程象父親般照顧它才行,即COM組件必須在一個進程內.那誰充當看護人的責任呢?先說說調度的問題。調度是個復雜的問題,以我的知識還講不清楚這個問題,我只是一般性的談談幾個最基本的概念。我們知道對于WIN32程序,每個進程都擁有4GB的虛擬地址空間,每個進程都有其各自的編址,同一個數據塊在不同的進程里的編址很可能就是不一樣的,所以存在著進程間的地址轉換問題。這就是調度問題。對于本地和遠程進程來說,DLL 和客戶程序在不同的編址空間,所以要傳遞接口指針到客戶程序必須要經過調度。Windows 已經提供了現成的調度函數,就不需要我們自己來做這個復雜的事情了。對遠程組件來說函數的參數傳遞是另外一種調度。DCOM是以RPC為基礎的,要在網絡間傳遞數據必須遵守標準的網上數據傳輸協議,數據傳遞前要先打包,傳遞到目的地后要解包,這個過程就是調度,這個過程很復雜,不過Windows已經把一切都給我們做好了,一般情況下我們不需要自己來編寫調度DLL。 我們剛說過一個COM組件必須在一個進程內。對于本地模式的組件一般是以EXE的形式出現,所以它本身就已經是一個進程。對于遠程DLL,我們必須找一個進程,這個進程必須包含了調度代碼以實現基本的調度。這個進程就是dllhost.exe。這是COM默認的DLL代理。實際上在分布式應用中,我們應該用MTS來作為DLL代理,因為MTS有著很強大的功能,是專門的用于管理分布式DLL組件的工具。 調度離我們很近又似乎很遠,我們編程時很少關注到它,這也是COM的一個優點之一,既平臺無關性,無論你是遠程的、本地的還是進程內的,編程是一樣的,一切細節都由COM自己處理好了,所以我們也不用深究這個問題,只要有個概念就可以了,當然如果你對調度有自己特殊的要求就需要深入了解調度的整個過程了,這里推薦一本COM+技術內幕,這絕對是一本講調度的好書。 (5) COM組件的核心是IDL。 我們希望軟件是一塊塊拼裝出來的,但不可能是沒有規定的胡亂拼接,總是要遵守一定的標準,各個模塊之間如何才能親密無間的合作,必須要事先共同制訂好它們之間交互的規范,這個規范就是接口。我們知道接口實際上都是純虛類,它里面定義好了很多的純虛函數,等著某個組件去實現它,這個接口就是兩個完全不相關的模塊能夠組合在一起的關鍵試想一下如果我們是一個應用軟件廠商,我們的軟件中需要用到某個模塊,我們沒有時間自己開發,所以我們想到市場上找一找看有沒有這樣的模塊,我們怎么去找呢?也許我們需要的這個模塊在業界已經有了標準,已經有人制訂好了標準的接口,有很多組件工具廠商已經在自己的組件中實現了這個接口,那我們尋找的目標就是這些已經實現了接口的組件,我們不關心組件從哪來,它有什么其它的功能,我們只關心它是否很好的實現了我們制訂好的接口。這種接口可能是業界的標準,也可能只是你和幾個廠商之間內部制訂的協議,但總之它是一個標準,是你的軟件和別人的模塊能夠組合在一起的基礎,是COM組件通信的標準。 COM具有語言無關性,它可以用任何語言編寫,也可以在任何語言平臺上被調用。但至今為止我們一直是以C+的環境中談COM,那它的語言無關性是怎么體現出來的呢?或者換句話說,我們怎樣才能以語言無關的方式來定義接口呢?前面我們是直接用純虛類的方式定義的,但顯然是不行的,除了C+誰還認它呢?正是出于這種考慮,微軟決定采用IDL來定義接口。說白了,IDL實際上就是一種大家都認識的語言,用它來定義接口,不論放到哪個語言平臺上都認識它。我們可以想象一下理想的標準的組件模式,我們總是從IDL開始,先用IDL制訂好各個接口,然后把實現接口的任務分配不同的人,有的人可能善長用VC,有的人可能善長用VB,這沒關系,作為項目負責人我不關心這些,我只關心你把最終的DLL 拿給我。這是一種多么好的開發模式,可以用任何語言來開發,也可以用任何語言來欣賞你的開發成果。 (6) COM組件的運行機制,即COM是怎么跑起來的。 這部分我們將構造一個創建COM組件的最小框架結構,然后看一看其內部處理流程是怎樣的 IUnknown *pUnk=NULL; IObject *pObject=NULL; CoInitialize(NULL); CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void*)&pUnk); pUnk-QueryInterface(IID_IOjbect, (void*)&pObject); pUnk-Release(); pObject-Func(); pObject-Release(); CoUninitialize(); 這就是一個典型的創建COM組件的框架,不過我的興趣在CoCreateInstance身上,讓我們來看看它內部做了一些什么事情。以下是它內部實現的一個偽代碼: CoCreateInstance(.) . IClassFactory *pClassFactory=NULL; CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void *)&pClassFactory); pClassFactory-CreateInstance(NULL, IID_IUnknown, (void*)&pUnk); pClassFactory-Release(); . 這段話的意思就是先得到類廠對象,再通過類廠創建組件從而得到IUnknown指針。繼續深入一步,看看CoGetClassObject的內部偽碼: CoGetClassObject(.) /通過查注冊表CLSID_Object,得知組件DLL的位置、文件名 /裝入DLL庫 /使用函數GetProcAddress(.)得到DLL庫中函數DllGetClassObject的函數指針。 /調用DllG
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 班級的一場比賽記事作文(12篇)
- 新興產業技術發展趨勢表
- 電影行業收入預測報告統計表
- 固廢綜合利用示范基地項目實施方案(參考范文)
- 學習中的一次挑戰與成功記事并議論文(12篇)
- 我的英雄贊美身邊英雄的話題作文14篇
- 體育設施與資源優化配置的實施路徑
- 建筑設計理論實踐練習題集
- 2025年藝術設計專業考試題及答案
- 2025年醫學影像技術與臨床應用的綜合能力考試卷及答案
- 大學生就業指導智慧樹知到期末考試答案2024年
- 試驗檢測單位安全培訓課件
- JBT 9848-2023 氣鎬 標準(正式版)
- 說寫做一致暨工藝紀律遵守課件
- 《國家電網公司電力安全工作規程(水電廠動力部分)》(一)
- 無菌技術操作規范護理課件
- 《產能分析報告》課件
- 珊瑚化石科普知識講座
- 中小學德育工作指南實施手冊
- (新版)職業健康綜合知識競賽題庫附答案
- 人教版九年級化學下冊第九單元《溶液》復習說課稿
評論
0/150
提交評論