浅析COM的思想及原理
?
?
一、COM編程思想--面向組件編程思想(COP)眾所周知,由C到C++,實現(xiàn)了由面向過程編程到面向?qū)ο缶幊痰倪^渡。而COM的出現(xiàn),又引出了面向組件的思想。其實,面向組件思想是面向?qū)ο笏枷氲囊环N延伸和擴展。因此,就讓我們先來回憶一下面向?qū)ο蟮乃枷氚伞?
?
面向?qū)ο笏枷胧菍⑺械牟僮饕约八僮鞯膶ο蠖歼M行歸類(由class實現(xiàn)),而它的目標(biāo)是要盡量提高代碼的可重用性(這也是面向?qū)ο笙啾让嫦蜻^程最大的優(yōu)點之一)。比如,有兩個程序A和B都需要對class C的對象進行操作,那么class C的代碼就可以重用了(即A和B都可以使用class C的代碼)。但是,對于這一點,面向?qū)ο笞龅貌⒉粔蚝谩_€是舉剛才的例子,程序A和B都要對class C的對象進行操作,那么,程序A和B的編程人員都必須將class C的代碼拷貝過來,然后重新編譯一次,這將是多么麻煩的事!況且,如果class C的代碼沒有公開,那這種重用就根本不可能實現(xiàn)了(除非程序A和B的編程人員和class C的編程人員是同一個人或者團隊,但這樣局限性就相當(dāng)大了)。
由于面向?qū)ο蟮倪@些局限性,很多程序員就會想,如果我們編程需要重用別人的成果時,不需要重新編譯別人的代碼那就好了。換句話說,我們要達到的目標(biāo)是,直接重用別人的成果而不是重用別人的代碼。這樣說也許很抽象,舉個例子大家就會比較明白。比如將class C的代碼編譯生成一個dll,那么當(dāng)其他程序員想要重用class C時,就只需要在自己的程序中加載這個dll而不需要重新編譯class C的代碼了(這也就是組件必須要能動態(tài)鏈接的原因)。正是這種思路引出了面向組件的編程思想。
下面,我就簡單介紹一下面向組件的思想。在以前,應(yīng)用程序總是被編寫成一個單獨的模塊,就是說一個應(yīng)用程序就是一個單獨的二進制文件。后來在引入了面向組件的編程思想后,原本單個的應(yīng)用程序文件被分隔成多個模塊來分別編寫,每個模塊具有一定的獨立性,也應(yīng)具有一定的與本應(yīng)用程序的無關(guān)性。一般來說,這種模塊的劃分是以功能作為標(biāo)準的。比如,一個網(wǎng)上辦公管理系統(tǒng),從功能上說它需要包含網(wǎng)絡(luò)通信、數(shù)據(jù)庫操作等部分,我們就可以將網(wǎng)絡(luò)通信和數(shù)據(jù)庫操作的部分分別提出來做成兩個獨立的模塊。那么,原本單個的應(yīng)用程序就分隔成了三個模塊:主控模塊、通信模塊和數(shù)據(jù)庫模塊。而這里的通信模塊和數(shù)據(jù)庫模塊還可以做得使其具有一定的通用性,那么其他的應(yīng)用程序也就可以利用這些模塊了。這樣做的好處有很多,比如當(dāng)對軟件進行升級的時候,只要對需要改動的模塊進行升級,然后用重新生成的一個新模塊來替換掉原來的舊模塊(但必須保持接口不變),而其他的模塊可以完全保持不變。這樣,軟件升級就變得更加方便,工作量也更小。
說了這么多,總結(jié)一下:面向組件編程思想,歸結(jié)起來就是四個字:模塊分隔。這里的“分隔”有兩層含義,第一就是要“分”,也就是要將應(yīng)用程序(尤其是大型軟件)按功能劃分成多個模塊;第二就是要“隔”,也就是每一個模塊要有相當(dāng)程度的獨立性,要盡量與其他模塊“隔”開。這四個字是面向組件編程思想的精華所在,也是COM的精華所在!理解了這四個字,也就真正理解了面向組件編程的思想。(這里說一點題外話,COM其實是一套規(guī)范或者說一套標(biāo)準,但是在我看來,COM的核心還在于它的思想,也就是面向組件編程思想。標(biāo)準誰都能定,但是思想只有一個!)
二、COM的優(yōu)點
COM的優(yōu)點也就是面向組件編程思想的優(yōu)點。而面向組件編程思想有很多的優(yōu)點,上面所說的便于軟件升級只是其中之一。對于它的優(yōu)點,我總結(jié)了一下,有下面幾條:
?
1、便于重用,使軟件開發(fā)更快捷
2、便于軟件升級
3、便于軟件開發(fā)的分工協(xié)作
4、便于用戶定制自己的應(yīng)用
以上幾點,第一和第二點都不用再多說了,前面講面向組件編程思想的部分里面已經(jīng)充分展示出了這兩點優(yōu)點。在這里我解釋一下第三和第四點。
如今的很多大型軟件,都不可能由某一個人單獨開發(fā),甚至不會由某一個公司去單獨開發(fā)。這是因為現(xiàn)在的很多大型軟件,綜合性太強,涉及的面也太廣。而一個人的精力是有限的,不可能學(xué)會這么多方面的知識,也不可能掌握到這么多方面的編程技術(shù),即使有可能,這樣做的效率也是很低下的。所以,通常的情況是分工協(xié)作。仍以前面提到的網(wǎng)上辦公管理系統(tǒng)為例,這個系統(tǒng)分為了三個模塊:主控模塊、通信模塊和數(shù)據(jù)庫模塊。由于這三個模塊具有相當(dāng)?shù)莫毩⑿?#xff0c;那么就可以將現(xiàn)有的所有開發(fā)人員分為三組,每一組負責(zé)一個模塊。而這三組之間,只需要商量好相互間的接口就可以了。這樣,對于每一個開發(fā)人員來說,就不需要掌握所有的編程技術(shù),甚至不需要了解其他模塊的具體實現(xiàn),而軟件仍然能有效的開發(fā)成功。這就是所謂的便于軟件開發(fā)的分工協(xié)作了。
除此之外,如果一個大型的軟件希望允許用戶在一定程度上定制自己的應(yīng)用,那么COM也是最好的選擇。比方說一個軟件由兩個模塊組成,模塊A和模塊B,現(xiàn)在軟件的開發(fā)商希望給予用戶一定的靈活性,希望可以允許用戶自己定制模塊B來實現(xiàn)自己特定的應(yīng)用,那么就只需要公開模塊B的所有接口;而用戶自己編程實現(xiàn)模塊B時也只需要實現(xiàn)了所有的這些接口就行了。當(dāng)然,這里面還有很多問題,比如COM組件的注冊,這涉及到COM標(biāo)準的一些細節(jié),在這里不作討論。
三、COM中的幾個重要概念?
1、組件:
其實只要你仔細閱讀了前面的部分,組件的概念應(yīng)該已經(jīng)很清楚了。這里所說的組件,就是前面反復(fù)在討論的所謂“模塊”。現(xiàn)在我只想強調(diào)一下組件需要滿足的一些條件。首先是封裝性,組件必須向外部隱藏其內(nèi)部的實現(xiàn)細節(jié),使從外部所能看到的只是接口。然后是組件必須能動態(tài)鏈接到一起,而不必像面向?qū)ο笾械腸lass一樣必須重新編譯。
2、接口:
??? 由于組件向外部隱藏了其內(nèi)部的細節(jié),因此客戶要使用組件時就必須通過一定的機制,也就是說要通過一定的方法來實現(xiàn)客戶與組件之間的通信,這就需要接口。所謂接口就是組件對外暴露的、向外部客戶提供服務(wù)的“連接點”。外部的客戶見不到組件內(nèi)部的細節(jié),它所能看到的只是接口,客戶也是通過接口來獲取組件提供的服務(wù)。這有點像OSI網(wǎng)絡(luò)協(xié)議分層模型,每一層就像一個組件,它內(nèi)部的實現(xiàn)細節(jié)對于其他層是不可見的;而每一層通過“服務(wù)接入點”向其上層提供服務(wù),這就像這里所說的接口。一般來說,接口總是固定的,也是公開的。組件的開發(fā)人員要實現(xiàn)這些接口,而客戶則通過接口獲得服務(wù)。正是接口的這種固定和公開,才使得組件和客戶能夠在不了解對方的情況下達成一致。
3、客戶:
這里所說的客戶不是指使用軟件的用戶,而是指要使用某一個組件的程序或模塊。也就是說,這里的客戶是相對組件來說的。
四、COM的實現(xiàn)原理與雛形模擬COM編程的一個重要特點就是要模塊化,說得具體一些,就是要將客戶和組件分隔開來,而客戶和組件之間又是通過接口來通信的。下面,我就介紹一下COM是怎樣將客戶與組件分隔開來,又是怎樣利用接口來實現(xiàn)客戶與組件間的通信的。
?
首先我要講講接口。COM中的接口實際上是一個函數(shù)地址表,當(dāng)組件實現(xiàn)了這個接口后,這個函數(shù)地址表中就填滿了組件所實現(xiàn)的那些接口函數(shù)的地址。而客戶也就是通過這個函數(shù)地址表獲得組件中那些接口函數(shù)的指針,從而獲得組件所提供的服務(wù)的。從某種意義上說,我們可以把接口理解為c++中的虛擬基類;或者說,在c++中可以用虛擬基類來實現(xiàn)接口!這是因為COM中規(guī)定的接口的存儲結(jié)構(gòu),和c++中的虛擬基類在內(nèi)存中的結(jié)構(gòu)是一致的。其存儲結(jié)構(gòu)如下圖:???
虛函數(shù)表 vtbl指針------>Fun1()指針--------> Fun2()指針--------> Fun3()指針--------> …………Vtbl指針指向一個虛函數(shù)表,而這個虛函數(shù)表的表項就是指向這些虛函數(shù)的指針。
?
接口有了,那么組件又是怎樣實現(xiàn)接口的呢?實際上,如果用虛擬基類來實現(xiàn)接口,那么組件就是對這個虛擬基類的繼承。大家知道,當(dāng)某個類繼承于一個虛擬基類的時候,它就要實現(xiàn)這個虛擬基類里聲明的虛函數(shù),這就正好與組件實現(xiàn)接口這一點相吻合。舉一個例子來說明,有一個接口InterfaceA,組件ComponentB要實現(xiàn)這個接口,那么就可以這樣用c++語言來描述:
//接口:class InterfaceA
{
? virtual void Fun1()=0;
? virtual void Fun2()=0;
}; //實現(xiàn)了接口InterfaceA的組件:
class ComponentB: public InterfaceA
{
? virtual void Fun1()
? {
???? printf("Fun1\n");
? }
? virtual void Fun2()
? {
???? printf("Fun2\n");
? } }; 而客戶只需要得到一個指向ComponentB實體的InterfaceA指針就可以獲得ComponentB組件的服務(wù)了: //使用了組件ComponentB的客戶:
……
ComponentB CB;
InterfaceA *pIA=&CB;? //獲得指向ComponentB實體的InterfaceA指針,以下客戶就可以只通過接口來獲取組件的服務(wù)
pIA->Fun1();
pIA->Fun2();
……
但是我們注意到,這樣做組件ComponentB和客戶還是沒有被完全分隔開。因為在客戶代碼里需要創(chuàng)建ComponentB實體,這對于只能看到接口而對組件一無所知的客戶來說,是不可以接受的(比如客戶不會知道組件的類名叫ComponentB)。解決這個問題的方法是在實現(xiàn)組件的動態(tài)鏈接文件(比如dll文件)里創(chuàng)建組件的實體,而不是在客戶代碼里創(chuàng)建組件實體。通常組件都是以dll的形式出現(xiàn)的,而在實現(xiàn)組件的dll里都會實現(xiàn)一個叫CreateInstance的函數(shù),這個函數(shù)可以被外部的客戶調(diào)用。它返回一個接口的指針,當(dāng)客戶調(diào)用這個函數(shù)后就能夠獲得指向組件實體的接口指針了。它的實現(xiàn)也很簡單: //在實現(xiàn)組件ComponentB的dll里:
InterfaceA *CreateInstance()
{
?? ComponentB CB;
?? InterfaceA *pIA=&CB;
?? return pIA;
}
當(dāng)然,真正的CreateInstance函數(shù)沒有這么簡單,我上面的代碼只是一個簡單的模擬。有個CreateInstance函數(shù)之后,客戶代碼就變成了: //使用了組件ComponentB的客戶:
……
InterfaceA *pIA=CreateInstance();? //獲得指向ComponentB實體的InterfaceA指針,以下客戶就可以只通過接口來獲取組件的服務(wù)
pIA->Fun1();
pIA->Fun2();
……
這樣,組件和客戶就完全被分隔開了,而連接它們的只有接口以及一個CreateInstance的函數(shù)。
?
以上就是COM的基本原理了。當(dāng)然,我前面也說了,COM其實是一套規(guī)范,它定義了很多標(biāo)準,比如COM規(guī)定每個接口都必須繼承于一個叫IUnknown的接口。我這里基本上沒有提及它的這些標(biāo)準,只是希望能通過對它進行一個簡單的模擬來說清楚它的實現(xiàn)原理。下面就給出我模擬COM機制實現(xiàn)的一套COM的雛形,希望能對大家理解COM有幫助。
1、實現(xiàn)了組件ComponentB的ComponentDll.dll:
?
//Interface.h
//接口
class InterfaceA
{
public:
? virtual void Fun1()=0;
? virtual void Fun2()=0;
};
//組件(實現(xiàn)了接口InterfaceA)
class ComponentB: public InterfaceA
{
public:
?virtual void Fun1()
?{
??printf("Fun1\n");
?}
?virtual void Fun2()
?{
??printf("Fun2\n");
?} }; //ComponentDll.cpp
//CreateInstance函數(shù)
ComponentB instance;
extern "C" _declspec(dllexport) InterfaceA *CreateInstance()
{
?InterfaceA *pIA=&instance;
?return pIA;
}
2、客戶Client.exe:
?
//Client.cpp
#include "Interface.h"
#pragma comment(lib,"ComponentDll")
int main(int argc, char* argv[])
{
?InterfaceA *pIA=0;
?pIA=CreateInstance();
?if(pIA!=0)
??pIA->Fun1();
?return 0;
}
總結(jié)
以上是生活随笔為你收集整理的浅析COM的思想及原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BGP的各种属性配置
- 下一篇: 社区网站功能实现系列(三):社区页面无刷