1. 接口查詢
關於IUnknown
所有的com接口都繼承了IUnknown,每個接口的前三個函數是QueryInterface、AddRef、Release。這使得所有的接口都可以當成IUnknown來使用。因此組件的任何一個接口都可以被客戶用來獲取他所支持的其他接口。
IUnknown指針的獲取
可以通過一個CreateInstance函數來返回一個IUnknown指針而不必使用new操作符
關於QueryInterface
通過QueryInterface函數來查詢某個組件是否支持某個特定的接口,若支持將返回一個指向這個接口的指針,否則返回一個錯誤代碼。
HRESULT __stdcallQueryInterface(const IID& riid, void** ppObject);
第一個參數表示客戶所需要的接口,是一“接口標識符”結構。
第二個參數是QueryInterface存放錯請求接口指針的地址。
返回一個HRESULT值,是一個32位的特定結構值。
QueryInterface的使用
假定用戶有一個指向IUknown的指針pI,則可如下:
voidfoo(IUnknown * pI)
{
//Define a pointer for the interface
IX*pIX = NULL;
//Ask for interface3 IX
HRESULThr = pI->QueryInterface(IID_IX, (void**)&pIX);
//Check return value
if(SUCCEEDED(hr))
pIX->Fx();
}
查詢了pI是否支持IID_IX所標識的接口。首先將pIX初始化爲NULL 是一個號的編程習慣
QueryInterface的實現
QueryInterface根據某個給定的IID返回相應的接口。支持做返回S_OK和相應的指針,不支持返回E_NOINTNTERFACE並將相應的指針返回值賦值爲NULL。以CA組件爲例:
interface IX : IUnknown{};
interface IY : IUnknown{};
class CA : public IX, publicIY{};
注:IUnknown並不是虛擬繼承,IX和IY並不是按虛擬繼承方式繼承IUnknown,這是由於會導致與Com不兼容的vtbl。
HRESULT __stdcallCA::QueryInterface(REFIID riid, void**ppvObject)
{
if(riid == IID_IUnknown)
*ppvObject= static_cast<IX*>(this);
else if(riid ==IID_IX)
*ppvObject= static_cast<IX*>(this);
else if(riid ==IID_IY)
*ppvObject= static_cast<IY*>(this);
else
{
*ppvObject= NULL;
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*ppvObject)->AddRef();
return S_OK;
}
關於類型轉換
IX指針所得到的地址與將其轉換成一個IY指針所得到的地址是不一樣的:
static_cast<IX*>(this) != static_cast<IY*>(this)
static_cast<void*>(this) != static_cast<IY*>(this)
一般在講this指針賦值給某個void指針時,應該先將其轉換成合適的類型,如下
*ppvObject = static_cast<IUnknown*>(this);
這時this指針轉換成IUnknown*時不明確的,因爲IX和IY都是有IUnknown繼承來的,所以應該是*ppvObject = static_cast<IUnknown*>(static_cast<IX*>(this));
C++中的多重繼承內存結構如下:(以CA爲例)
2. 關於QueryInterface的實現規則
規則:
- QueryInterface返回的總是統一IUnknown指針
- 若用戶曾經獲取過某個接口,那麼他將總能獲取該接口
- 客戶可以再次獲取已經擁有的接口
- 客戶可以返回到起始接口
- 若能夠從某個接口獲取特定接口,那麼可以從任意接口都將可以獲取此接口
同一IUnknown
組件的實例只有一個IUnknown接口,當查詢實例的IUnknown接口時,不論通過哪個接口得到的將是同一指針。
BOOL SameComponents(IX* pIX, IY* pIY)
{
IUnknown*pI1 = NULL;
IUnknown*pI2 = NULL;
//get IUnknown from IX
pIX->QueryInterface(IID_IUnknown,(void**)&pI1);
//Get IUnknown from IY
pIY->QueryInterface(IID_IUnknown,(void**)&pI2);
//are same??
return (pI1 == pI2);
}
客戶可以獲取曾經得到的接口
如果組件所支持的接口會不時的發生變化將會導致客戶編寫代碼極爲困難,將無法通過編程的方法來決定一個組件到底具有什麼樣的功能。
可以再次獲取已經擁有的接口
如果客戶擁有一個IX接口,則可以通過IX獲取IX的接口指針,並且一定成功。
客戶可以從任何接口返回到起始接口
若客戶擁有一個IX接口指針併成功的使用它來獲取一天IY接口,那麼將可以使用IY來查詢一個IX接口。
若能從某接口獲取某特定接口,則從任意接口都將能夠獲取此接口
例如可以通過從接口IX獲取IY,通過IY接口可以獲取IZ接口,那麼從IX也講可以獲取IZ接口。
3. QueryInterface定義了組件
一個組件實際就是有QueryInterface定義了,組件所支持的接口集就是QueryInterface能夠爲之返回接口指針的那些接口。客戶瞭解組件支持的接口唯一方法就是查詢。