COM编程之二 接口
【1】接口
DLL的接口是它所輸出的那些函數(shù)。
C++類的接口是該類的一個(gè)成員函數(shù)集。
COM接口是包含一個(gè)函數(shù)指針數(shù)組的內(nèi)存結(jié)構(gòu)。
每一個(gè)數(shù)組元素包含的是一個(gè)由組件所實(shí)現(xiàn)的函數(shù)的地址。
在COM中接口就是一切。對(duì)于客戶而言,一個(gè)組件就是一個(gè)接口集。
客戶只能通過(guò)接口才能同COM組件打交道。
對(duì)程序員而言,接口對(duì)于一個(gè)應(yīng)用程序是最重要的,組件本身只不過(guò)是接口的實(shí)現(xiàn)細(xì)節(jié)。
一個(gè)接口是一個(gè)函數(shù)集合,一個(gè)組件則是一個(gè)接口集,而一個(gè)系統(tǒng)則是一系列組件的集合。
如下圖更為精確地顯示了接口對(duì)于COM程序的重要性:
【2】COM接口的優(yōu)點(diǎn)
1、COM接口可以保護(hù)系統(tǒng)免受外界變化的影響。
2、COM接口使得客戶可以用同樣的方式來(lái)處理不同的組件。(這種能力即多態(tài))
3、COM接口可以使內(nèi)部具體實(shí)現(xiàn)得以隱藏。
【3】COM接口的C++實(shí)現(xiàn)
實(shí)現(xiàn)代碼如下:
1 class IX //First interface 2 { 3 public: 4 virtual void Fx1() = 0; 5 virtual void Fx2() = 0; 6 }; 7 8 class IY //Second interface 9 { 10 virtual void Fy1() = 0; 11 virtual void Fy2() = 0; 12 }; 13 14 class CA : public IX, public IY //Component 15 { 16 public: 17 //Implemention of abstract base calss IX 18 virtual void Fx1() { cout << "Fx1" << endl; } 19 virtual void Fx2() { cout << "Fx2" << endl; } 20 //Implemention of abstract base class IY 21 virtual void Fy1() { cout << "Fy1" << endl; } 22 virtual void Fy2() { cout << "Fy2" << endl; } 23 };可見(jiàn),IX和IY是實(shí)現(xiàn)接口的純抽象基類。
純抽象基類指僅包含純虛函數(shù)的基類。
純虛函數(shù)在派生類中必需實(shí)現(xiàn)。否則 派生類仍不可實(shí)例化。
對(duì)純虛函數(shù)的繼承被稱為接口繼承。
CA是組件,IX、IY是接口,CA同時(shí)實(shí)現(xiàn)IX和IY兩個(gè)接口。
一個(gè)COM組件可以有多個(gè)接口。由多重繼承實(shí)現(xiàn)。
COM接口在C++中是使用純抽象基類來(lái)實(shí)現(xiàn)的。
一個(gè)C++類可以使用多繼承來(lái)實(shí)現(xiàn)一個(gè)可以提供多個(gè)接口的組件。
一個(gè)COM組件可以由多個(gè)C++類來(lái)完成,如可以再包含CFactory類。
一個(gè)COM組件也可以不包含任何C++類,如用C語(yǔ)言實(shí)現(xiàn)。
IX和IY并非真正意義的COM接口,真正的COM接口必須繼承一個(gè)名為IUnknown的接口。
【4】識(shí)別interface關(guān)鍵字的本質(zhì)
為了避免將接口定義成一個(gè)類的形式,微軟在OBJBASE.H中定義如下:
#define interface struct
定義為struct的原因在于struct的成員將自動(dòng)具有共有屬性,因此無(wú)需因加public造成不必要的混亂。
【5】圖形化表示接口與組件
圖形化表示接口與組件。如下圖所示:
【6】如何解決名稱沖突?
當(dāng)一個(gè)組件包含有多個(gè)接口時(shí),可能會(huì)發(fā)生名稱沖突。
一是接口名稱之間的沖突,
二是一個(gè)接口內(nèi)部的函數(shù)與另外一個(gè)接口內(nèi)部的函數(shù)名稱的沖突。
可以用下列方法來(lái)解決:
1、接口名稱的沖突可以使用某種簡(jiǎn)單的約定進(jìn)行有效的避免。
如在接口名稱前加公司名稱或產(chǎn)品名稱。
如產(chǎn)品Xyz的IFly接口,可以使用IXyzFly接口名稱。
2、函數(shù)名稱的沖突亦可類似接口名稱方法進(jìn)行解決。
如IXyzFly::Fly與IAbcFly::Fly,
可以改為:IXyzFly::XyzFly與IAbcFly:: AbcFly
3、不使用多重接口繼承。
4、使用包容與聚合技術(shù)。
注:COM接口是一個(gè)二進(jìn)制內(nèi)存標(biāo)準(zhǔn),客戶同接口的連接是通過(guò)接口在其表示的內(nèi)存塊中的位置完成的,而不是通過(guò)接口名稱
或其成員函數(shù)名稱完成。因此COM并不關(guān)心接口的名稱或其成員函數(shù)的名稱。
【7】接口的不變性
接口的不變性是指一旦接口被公布,那么它將永遠(yuǎn)保持不變。
1、對(duì)組件升級(jí)時(shí),一般不會(huì)修改原有的接口,而是根據(jù)需要適當(dāng)?shù)脑黾右恍┬碌慕涌趤?lái)擴(kuò)展其原有的功能。
2、通過(guò)使用多重接口的繼承技術(shù),使得這些組件不但能夠支持原有的接口,還可以支持新的接口。
因此多重繼承為組件和客戶可以智能對(duì)同對(duì)方的新版本進(jìn)行交互打下了扎實(shí)的基礎(chǔ)。
【8】接口的多態(tài)性
多態(tài)性是指可以按照同一種方式來(lái)處理不同的對(duì)象。
若兩個(gè)不同的組件支持同一個(gè)接口,那么客戶可以使用相同的代碼來(lái)處理其中任何的一個(gè)組件,也就是說(shuō),客戶可以使用相同的方式來(lái)處理不同的組件,從而實(shí)現(xiàn)了接口的多態(tài)性。
多重接口的支持能力為接口的多態(tài)提供了更多的機(jī)會(huì),也使得多態(tài)的重要性更為突出。因?yàn)槎鄳B(tài)性可以使代碼更加有效的被復(fù)用。
【9】COM接口內(nèi)存結(jié)構(gòu)
COM接口的內(nèi)存結(jié)構(gòu)同C++編譯器為抽象基類所生成的內(nèi)存結(jié)構(gòu)是相同的。
如下純抽象基類:
1 interface IX 2 { 3 virtual void __stdcall Fx1() = 0; 4 virtual void __stdcall Fx2() = 0; 5 virtual void __stdcall Fx3() = 0; 6 virtual void __stdcall Fx4() = 0; 7 };如下抽象基類所定義的內(nèi)存接口示例:
一個(gè)純抽象基類所定義的內(nèi)存結(jié)構(gòu)分為兩部分。
左邊是一個(gè)指向虛函數(shù)表的指針(簡(jiǎn)稱為vtbl指針)
右邊是一個(gè)虛擬函數(shù)表,其中包含一組指向虛擬函數(shù)實(shí)現(xiàn)的指針。
而指向抽象基類的指針將指向vtbl指針。
而所有的COM接口都必須繼承一個(gè)名為IUnknown的接口,這意味著所有的COM接口的前三個(gè)項(xiàng)都是相同的,其中保存的是IUnknown中三個(gè)成員函數(shù)實(shí)現(xiàn)的地址。
備注:接口的內(nèi)存結(jié)構(gòu)在不同的操作系統(tǒng)上可能不同。
【10】vtbl指針的作用
vtbl指針在由pIX接口指針到虛擬函數(shù)表的過(guò)程中,看上去增加了一個(gè)額外的級(jí)別,有什么作用呢?
(1) C++編譯器生成代碼時(shí),實(shí)現(xiàn)抽象基類的派生類會(huì)將特定的實(shí)例的信息(如成員數(shù)據(jù))同vtbl一起保存。
假設(shè)類CA派生于IX,并擁有自己的成員數(shù)據(jù)m_dA。
單個(gè)實(shí)例時(shí):pA = new CA;
pA內(nèi)存結(jié)構(gòu)圖如下(實(shí)例數(shù)據(jù)以黑色背景表示):
?
(2)同一個(gè)類的不同實(shí)例可以共享同一個(gè)vtbl
若我們建立CA兩個(gè)不同的實(shí)例,那么將會(huì)有兩組不同的實(shí)例數(shù)據(jù)。
但不同的實(shí)例可以共享同一vtbl以及相同的實(shí)現(xiàn)。
如下代碼:
1 void main() 2 { 3 //Create first instance of CA 4 CA* pA1 = new CA(1.5); 5 //Create second instance of CA 6 CA* pA2 = new CA(2.75); 7 }同一類的多個(gè)實(shí)例共享vtbl,如下圖:
?
(3) 多態(tài)
代碼如下:
1 #include <iostream> 2 using namespace std; 3 #include <objbase.h> 4 5 interface IX 6 { 7 virtual void __stdcall Fx1() = 0; 8 virtual void __stdcall Fx2() = 0; 9 virtual void __stdcall Fx3() = 0; 10 virtual void __stdcall Fx4() = 0; 11 }; 12 13 class CA : public IX 14 { 15 public: 16 virtual void __stdcall Fx1() { cout << "CA::Fx1" << endl; } 17 virtual void __stdcall Fx2() { cout << m_dFx2 << endl; } 18 virtual void __stdcall Fx3() { cout << m_dFx3 << endl; } 19 virtual void __stdcall Fx4() { cout << m_dFx4 << endl; } 20 21 CA(double dValue = 0) 22 : m_dFx2(dValue * dValue) 23 , m_dFx3(dValue * dValue * dValue) 24 , m_dFx4(dValue * dValue * dValue * dValue) 25 {} 26 27 private: 28 double m_dFx2; 29 double m_dFx3; 30 double m_dFx4; 31 }; 32 33 class CB : public IX 34 { 35 public: 36 virtual void __stdcall Fx1() { cout << "CB::Fx1" << endl; } 37 virtual void __stdcall Fx2() { cout << "CB::Fx2" << endl; } 38 virtual void __stdcall Fx3() { cout << "CB::Fx3" << endl; } 39 virtual void __stdcall Fx4() { cout << "CB::Fx4" << endl; } 40 }; 41 42 void Fun(IX* pIx) 43 { 44 pIx->Fx1(); 45 pIx->Fx2(); 46 pIx->Fx3(); 47 pIx->Fx4(); 48 } 49 50 void main() 51 { 52 //create instance of CA 53 CA* pA = new CA(10); 54 //create instance of CB 55 CB* pB = new CB; 56 //get IX Pointer to CA 57 IX* pIx = pA; 58 Fun(pIx); 59 //get IX Pointer to CB 60 pIx = pB; 61 Fun(pIx); 62 } 63 64 //Output 65 /* 66 CA::Fx1 67 100 68 1000 69 10000 70 CB::Fx1 71 CB::Fx2 72 CB::Fx3 73 CB::Fx4 74 */通過(guò)一個(gè)共同的抽象基類來(lái)一致的使用兩個(gè)不同的類,內(nèi)存結(jié)構(gòu)如下圖:
備注:
1、上圖的實(shí)例數(shù)據(jù)用空框表示,因?yàn)镃OM一般都不關(guān)心實(shí)例數(shù)據(jù)。
2、兩個(gè)虛擬表的結(jié)構(gòu)相同,即兩個(gè)表中的函數(shù)Fxi所占據(jù)的表格項(xiàng)都在第i項(xiàng)。
?
Good Good Study, Day Day Up.
順序 ?選擇 ?循環(huán) ?總結(jié)
總結(jié)
以上是生活随笔為你收集整理的COM编程之二 接口的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: COM编程之一 组件
- 下一篇: COM编程之四 引用计数