COM:IUnknown、IClassFactory、IDispatch

來源:互聯網
上載者:User

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的代碼就會驚歎它實現的複雜了,因為你必須考慮各種參數類型的情況,所幸我們不需要自己來做這件事,而且可能永遠也沒這樣的機會。:-)

(2) 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介面派生的類,顯然它就只能用虛函數表的方式來調用介面了

(3) 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+技術內幕》,這絕對是一本講調度的好書。

(4) COM組件的核心是IDL。

我們希望軟體是一塊塊拼裝出來的,但不可能是沒有規定的胡亂拼接,總是要遵守一定的標準,各個模組之間如何才能親密無間的合作,必須要事先共同制訂好它們之間互動的規範,這個規範就是介面。我們知道介面實際上都是純虛類,它裡面定義好了很多的純虛函數,等著某個組件去實現它,這個介面就是兩個完全不相關的模組能夠組合在一起的關鍵試想一下如果我們是一個應用軟體廠商,我們的軟體中需要用到某個模組,我們沒有時間自己開發,所以我們想到市場上找一找看有沒有這樣的模組,我們怎麼去找呢?也許我們需要的這個模組在業界已經有了標準,已經有人制訂好了標準的介面,有很多組件工具廠商已經在自己的組件中實現了這個介面,那我們尋找的目標就是這些已經實現了介面的組件,我們不關心組件從哪來,它有什麼其它的功能,我們只關心它是否很好的實現了我們制訂好的介面。這種介面可能是業界的標準,也可能只是你和幾個廠商之間內部制訂的協議,但總之它是一個標準,是你的軟體和別人的模組能夠組合在一起的基礎,是COM組件通訊的標準。

COM具有語言無關性,它可以用任何語言編寫,也可以在任何語言平台上被調用。但至今為止我們一直是以C++的環境中談COM,那它的語言無關性是怎麼體現出來的呢?或者換句話說,我們怎樣才能以語言無關的方式來定義介面呢?前面我們是直接用純虛類的方式定義的,但顯然是不行的,除了C++誰還認它呢?正是出於這種考慮,微軟決定採用IDL來定義介面。說白了,IDL實際上就是一種大家都認識的語言,用它來定義介面,不論放到哪個語言平台上都認識它。我們可以想象一下理想的標準的組件模式,我們總是從IDL開始,先用IDL制訂好各個介面,然後把實現介面的任務分配不同的人,有的人可能善長用VC,有的人可能善長用VB,這沒關係,作為項目負責人我不關心這些,我只關心你把最終的DLL 拿給我。這是一種多麼好的開發模式,可以用任何語言來開發,也可以用任何語言來欣賞你的開發成果。

(5) 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的函數指標。
//調用DllGetClassObject
}
DllGetClassObject是幹什麼的,它是用來獲得類廠對象的。只有先得到類廠才能去建立組件.
下面是DllGetClassObject的偽碼:
DllGetClassObject(...)
{
......
CFactory* pFactory= new CFactory; //類廠對象
pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);
//查詢IClassFactory指標
pFactory->Release();
......
}
CoGetClassObject的流程已經到此為止,現在返回CoCreateInstance,看看CreateInstance的偽碼:
CFactory::CreateInstance(.....)
{
...........
CObject *pObject = new CObject; //組件對象
pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);
pObject->Release();
...........
}

(6) 一個典型的自註冊的COM DLL所必有的四個函數

  DllGetClassObject:用於獲得類廠指標
  DllRegisterServer:註冊一些必要的資訊到註冊表中
DllUnregisterServer:卸載註冊資訊
DllCanUnloadNow:系統空閑時會調用這個函數,以確定是否可以卸載DLL
DLL還有一個函數是DllMain,這個函數在COM中並不要求一定要實現它,但是在VC產生的組件中自動都包含了它,它的作用主要是得到一個全域的執行個體對象。

(7) 註冊表在COM中的重要作用

首先要知道GUID的概念,COM中所有的類、介面、類型庫都用GUID來唯一標識,GUID是一個128位的字串,根據特製演算法產生的GUID可以保證是全世界唯一的。 COM組件的建立,查詢介面都是通過註冊表進行的。有了註冊表,應用程式就不需要知道組件的DLL檔案名稱、位置,只需要根據CLSID查就可以了。當版本升級的時侯,只要改一下註冊表資訊就可以神不知鬼不覺的轉到新版本的DLL。 

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/l12345678/archive/2007/07/25/1707629.aspx#808658

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.