【1】IUnknown接口
客戶同組件交互都是通過接口完成的。
在客戶查詢組件的其它接口時,也是通過接口完成的。而那個接口就是IUnknown。
IUnknown接口的定義包含在Win32SDK中的UNKNEN.h頭文件中。引用如下:
1 interface IUnknown 2 { 3 virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv0 = 0; 4 virtual ULONG __stdcall AddRef() = 0; 5 virtual ULONG __stdcall Release() = 0; 6 };
【2】COM接口內存結構
所有的COM接口都繼承自IUnknown接口。
所以每個COM接口的vtbl中的前三個函數都是相同的。
因此每個COM接口都支持QueryInterface
從而組件的任何一個COM接口都可以被客戶用來獲取它所支持的其它COM接口。
同時所有的接口也將是IUnknown接口指針。
進一步而言,客戶並不需要單獨維護一個代表組件的指針,它所關心的僅僅是接口指針。
如果某個接口的vtbl中的前三個函數不是這個三個,那么它將不是一個COM接口。
COM接口內存結構如下圖所示:
【3】QueryInterface函數
IUnknown中包含一個名稱為QueryInterface的成員函數。
客戶可以通過此函數來查詢某組件是否支持某個特定的接口。
若支持,QueryInterface函數將返回一個指向此接口的指針。
否則,返回值將是一個錯誤代碼。
QueryInterface函數原型如下:
HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
第一個參數客戶欲查詢的接口的標識符。一個標識所需接口的常量。
第二個參數是存放所請求接口指針的地址
返回值是一個HRESULT值。成功為S_OK;失敗為E_NOINTERFACE。
QueryInterface函數是使用。代碼如下:
1 void Fun(IUnknown* pl) 2 { 3 //Define a pointer for the interface
4 IX* pIx = NULL; 5 //Ask for interface IX
6 HRESULT hr = pl->QueryInterface(IID_IX, (void**)&pIx); 7 //Check return value
8 if (SUCCEEDED(hr)) 9 { 10 //Use interface
11 pIx->Fx1(); 12 } 13 }
【4】一個完整的使用例子
完整代碼如下:
1 #include <iostream>
2 using namespace std; 3 #include <objbase.h>
4
5 void trace(const char* msg) 6 { 7 cout << msg << endl; 8 } 9
10 // 接口定義
11 interface IX : IUnknown 12 { 13 virtual void __stdcall Fx() = 0; 14 }; 15
16 interface IY : IUnknown 17 { 18 virtual void __stdcall Fy() = 0; 19 }; 20
21 interface IZ : IUnknown 22 { 23 virtual void __stdcall Fz() = 0; 24 }; 25
26 // Forward references for GUIDs
27 extern const IID IID_IX; 28 extern const IID IID_IY; 29 extern const IID IID_IZ; 30
31 //
32 // 實現接口 IX, IY(這里表示一個組件) 33 // 34 class CA : public IX, public IY 35 { 36 //IUnknown implementation
37 virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv); 38 virtual ULONG __stdcall AddRef() { return 0;} 39 virtual ULONG __stdcall Release() { return 0;} 40
41 // Interface IX implementation
42 virtual void __stdcall Fx() { cout << "這里是Fx函數" << endl;} 43
44 // Interface IY implementation
45 virtual void __stdcall Fy() { cout << "這里是Fy函數" << endl;} 46 }; 47
48 HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv) 49 { 50 if (iid == IID_IUnknown) 51 { 52 trace("QueryInterface: Return pointer to IUnknown."); 53 *ppv = static_cast<IX*>(this); 54 } 55 else if (iid == IID_IX) 56 { 57 trace("QueryInterface: Return pointer to IX."); 58 *ppv = static_cast<IX*>(this); 59 } 60 else if (iid == IID_IY) 61 { 62 trace("QueryInterface: Return pointer to IY."); 63 *ppv = static_cast<IY*>(this); 64 } 65 else
66 { 67 trace("QueryInterface: Interface not supported."); 68 *ppv = NULL; 69 return E_NOINTERFACE; 70 } 71 reinterpret_cast<IUnknown*>(*ppv)->AddRef(); // 加計數
72 return S_OK; 73 } 74
75 //
76 // 創建類CA,並返回一個指向IUnknown的指針 77 // 78 IUnknown* CreateInstance() 79 { 80 IUnknown* pI = static_cast<IX*>(new CA); 81 pI->AddRef(); 82 return pI ; 83 } 84
85 //
86 // 下面是各接口的IID 87 //
88 // {32bb8320-b41b-11cf-a6bb-0080c7b2d682}
89 static const IID IID_IX =
90 {0x32bb8320, 0xb41b, 0x11cf, 91 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; 92
93 // {32bb8321-b41b-11cf-a6bb-0080c7b2d682}
94 static const IID IID_IY =
95 {0x32bb8321, 0xb41b, 0x11cf, 96 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; 97
98 // {32bb8322-b41b-11cf-a6bb-0080c7b2d682}
99 static const IID IID_IZ =
100 {0x32bb8322, 0xb41b, 0x11cf, 101 {0xa6, 0xbb, 0x0, 0x80, 0xc7, 0xb2, 0xd6, 0x82}}; 102
103 //
104 // 主函數(這里代表客戶) 105 // 106 int main() 107 { 108 HRESULT hr; 109
110 trace("Client:獲取 IUnknown指針."); 111 IUnknown* pIUnknown = CreateInstance(); 112
113 trace("Client:獲取接口IX."); 114
115 IX* pIX = NULL; 116 hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX); 117 if (SUCCEEDED(hr)) 118 { 119 trace("Client:獲取接口IX成功."); 120 pIX->Fx(); // 使用 IX.
121 } 122
123 trace("Client:獲取接口IY."); 124
125 IY* pIY = NULL; 126 hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY); 127 if (SUCCEEDED(hr)) 128 { 129 trace("Client: Succeeded getting IY."); 130 pIY->Fy(); // 使用 IY.
131 } 132
133 trace("Client:是否支持接口IZ."); 134
135 IZ* pIZ = NULL; 136 hr = pIUnknown->QueryInterface(IID_IZ, (void**)&pIZ); 137 if (SUCCEEDED(hr)) 138 { 139 trace("Client:獲取接口IZ成功."); 140 pIZ->Fz(); 141 } 142 else
143 { 144 trace("Client:獲取接口IZ失敗,不支持接口IZ."); 145 } 146
147 trace("Client:用接口IX查詢接口IY."); 148
149 IY* pIYfromIX = NULL; 150 hr = pIX->QueryInterface(IID_IY, (void**)&pIYfromIX); 151 if (SUCCEEDED(hr)) 152 { 153 trace("Client:獲取接口IY成功."); 154 pIYfromIX->Fy(); 155 } 156
157 trace("Client:用接口IY查詢接口IUnknown."); 158
159 IUnknown* pIUnknownFromIY = NULL; 160 hr = pIY->QueryInterface(IID_IUnknown, (void**)&pIUnknownFromIY); 161 if (SUCCEEDED(hr)) 162 { 163 cout << "IUnknown指針是否相等?"; 164 if (pIUnknownFromIY == pIUnknown) 165 { 166 cout << "Yes, pIUnknownFromIY == pIUnknown." << endl; 167 } 168 else
169 { 170 cout << "No, pIUnknownFromIY != pIUnknown." << endl; 171 } 172 } 173
174 // Delete the component.
175 delete pIUnknown; 176
177 return 0; 178 } 179
180 //Output
181 /*
182 Client:獲取 IUnknown指針. 183 Client:獲取接口IX. 184 QueryInterface: Return pointer to IX. 185 Client:獲取接口IX成功. 186 這里是Fx函數 187 Client:獲取接口IY. 188 QueryInterface: Return pointer to IY. 189 Client: Succeeded getting IY. 190 這里是Fy函數 191 Client:是否支持接口IZ. 192 QueryInterface: Interface not supported. 193 Client:獲取接口IZ失敗,不支持接口IZ. 194 Client:用接口IX查詢接口IY. 195 QueryInterface: Return pointer to IY. 196 Client:獲取接口IY成功. 197 這里是Fy函數 198 Client:用接口IY查詢接口IUnknown. 199 QueryInterface: Return pointer to IUnknown. 200 IUnknown指針是否相等?Yes, pIUnknownFromIY == pIUnknown. 201 */
【5】多重繼承及類型轉換
一般將一種類型的指針轉換成另外一種類型的指針並不會改變它的值。
但是為了支持多重繼承,在某些情況下,C++必須改變類指針的值。
例如:
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 }; 8
9 interface IY 10 { 11 virtual void __stdcall Fy1() = 0; 12 virtual void __stdcall Fy2() = 0; 13 virtual void __stdcall Fy3() = 0; 14 virtual void __stdcall Fy4() = 0; 15 }; 16
17 class CA : public IX, public IY 18 { 19 virtual void __stdcall Fx1() { cout << "IX::Fx1" << endl;} 20 virtual void __stdcall Fx2() { cout << "IX::Fx2" << endl;} 21 virtual void __stdcall Fx3() { cout << "IX::Fx3" << endl;} 22 virtual void __stdcall Fx4() { cout << "IX::Fx4" << endl;} 23
24 virtual void __stdcall Fy1() { cout << "IY::Fy1" << endl;} 25 virtual void __stdcall Fy2() { cout << "IY::Fy2" << endl;} 26 virtual void __stdcall Fy3() { cout << "IY::Fy3" << endl;} 27 virtual void __stdcall Fy4() { cout << "IY::Fy4" << endl;} 28 }; 29
30 void FunX(IX* pIx) 31 { 32 cout<<"pIx:"<<" "<< pIx <<endl; 33 pIx->Fx1(); 34 pIx->Fx2(); 35 pIx->Fx3(); 36 pIx->Fx4(); 37 } 38
39 void FunY(IY* pIy) 40 { 41 cout<<"pIy:"<<" "<< pIy <<endl; 42 pIy->Fy1(); 43 pIy->Fy2(); 44 pIy->Fy3(); 45 pIy->Fy4(); 46 } 47
48 void main() 49 { 50 CA* pA = new CA; 51 cout<<"pA:"<<" "<<pA<<endl; 52
53 FunX(pA); 54 FunY(pA); 55
56 delete pA; 57 pA = NULL; 58 } 59
60 //Output
61 /*
62 pA: 00494B80 63 pIx: 00494B80 64 IX::Fx1 65 IX::Fx2 66 IX::Fx3 67 IX::Fx4 68 pIy: 00494B84 69 IY::Fy1 70 IY::Fy2 71 IY::Fy3 72 IY::Fy4 73 */
由於CA同時繼承了IX和IY,因此在可以使用IX或IY指針的地方均可以使用指向CA的指針。
FunX需要一個指向合法的IX的虛函數表的指針。
FunY則需要一個指向IY虛函數表的指針。
而IX和IY的虛函數表中的內容是不一樣的。
編譯器將同一指針傳給FunX和FunY是不可能的。
必須對CA的指針進行修改以便它指向一個合適的vtbl指針。
同時繼承IX和IY的類CA的內存結構,如圖所示:
由示例代碼運行結果以及上圖可知:
CA的this指針指向IX的虛函數表。所以可以不改變CA的this指針用它來代替IX指針。
CA的this指針沒有指向IY的虛函數表指針。所以在將指向類CA的指針傳給一個接收IY指針的函數之前,其值必須修改。
編譯器將把IY虛擬函數表指針的偏移量(△IY)加到CA的this指針上。
IY* pC = pA;
與之等價代碼:
IY* pC = (char*)pA + △IY;
【6】QureryInterface的實現規則有哪些?
(1)QureryInterface返回的總是同一IUnkown地址。
如果QureryInterface的實現不遵循此規則,將無法決定兩個接口是否屬於同一組件。
判斷兩個接口是否屬於同一個組件的代碼實現如下:
1 BOOL IsSameComponent(IX* pIx, IY* pIy) 2 { 3 IUnknown* pI1 = NULL; 4 IUnknown* pI2 = NULL; 5 //Get IUnknown pointer from pIx
6 pIx->QueryInterface(IID_IUnknown, (void**)&pI1); 7 //Get IUnknown pointer from pIy
8 pIy->QueryInterface(IID_IUnknown, (void**)&pI2); 9 //Are the two IUnknown pointer equal ?
10 return pI1 == pI2; 11 }
(2)若客戶曾經獲取過某個接口,那么它將總能獲取此接口。
如果客戶不能獲取它曾經使用過的某個接口,則說明組件的接口集是不固定的,客戶也將無法通過編程的方法來決定一個
組件到底具有一些什么樣的功能。
(3)客戶可以再次獲取已擁有的接口。
(4)客戶可以返回到起始接口。
若客戶擁有一個IX接口指針並成功的使用它查詢了一個IY接口,那么它將可以使用此IY接口來查詢一個IX接口。
換而言之,不論客戶所擁有的接口是什么,它都可以獲取起始時所用的接口。
(5)若能從從某個接口獲取某個特定的接口,那么可以從任意接口都可以獲取此接口。
(6)客戶能夠使用任何IUnkown接口獲取該組件所支持的任何接口。
制定上述規則的目的完全是為了使QureryInterface使用起來更為簡單、更富有邏輯性、更一致性以及更具有確定性。
不過幸運的是,實現上述規則並不難,並且只有組件按照這些規則正確的實現了QureryInterface時,客戶才不會為此擔心。
【7】客戶如何知道組件支持的接口?
由於客戶並不知道QureryInterface的實現,也不像C++中的擁有類的頭文件,所以客戶了解組件的唯一方法就是使用QureryInterface來查詢。
【8】組件的新版本
當組件發布一個新的接口並被用戶使用之后,此接口將絕不允許發生任何變化。
當我們要升級該接口時,可以建立一個新的接口並為它指定新的IID。
當客戶用QureryInterface查詢老的IID時,它將返回老的接口,而當它查詢新的IID時,它將返回升級過的接口。
就QureryInterface而言,一個IID就是一個接口。接口的標識(IID)是同其版本綁在一起的。
也就是說該接口升級為新的版本,IID也需要更新。
假設有一個組件Bronce,它擁有一個IFly接口,使用該組件的客戶為Pilot。 經過一段時間后,組件和客戶都進行了升級。
Bronce組件升級為FastBronce,其接口也升級為IFastFly。
Pilot客戶升級為FastPilot,既支持組件新的接口也支持老的接口。
下圖給出了它們之間各種可能的運行組合:
不論按何種組合,客戶和組件都能夠正常運行,因此該升級是非常平滑而又無縫的,且也是非常之有效的。
【9】何時需要建立組件的新版本?
為使COM版本處理多個機制能夠起作用,我們在為已有的接口制定新的IID時應該要非常謹慎。
當改變了下列任何條件之一時,都應該為接口制定新的IID:
1、接口中函數的數目。
2、接口中函數的順序。
3、某個函數的參數。
4、某個函數的參數的順序。
5、某個函數參數的類型。
6、函數可能的返回值。
7、函數返回值的類型。
8、函數參數的含義。
9、接口中函數的含義。
總之,只要是所做的修改如果會導致已有客戶不能正常運行,都應該為接口制定新的ID。
如果能夠同時修改客戶和組件,則可以靈活掌握上述條款。
【10】命名組件新版本的規則
在建立了新的版本之后,也應當相應的修改其名稱。
COM關於新版本名稱的約定是在老的版本之后加一個數字。
如IFly新的版本名稱應該是IFly2。
Good Good Study, Day Day Up.
順序 選擇 循環 總結