前言
組件對外公布的是介面;一個組件可以實現多個介面,也就是說可以對外公布多個介面,之前也總結過了,你很少會100%的去完全瞭解一個組件的所有介面,就像你去學習編程一樣,你幾乎不可能去成為編程中的全才。那麼,既然我們不能去完全的瞭解一個組件提供的所有介面,那麼我們在實際開發中,如何去判斷一個組件是否提供對應的介面呢?看文檔?是的,是個好主意,在文檔的海洋,找到一個知識點,真的很難,浪費時間和精力;其實,組件本身就提供對自己查詢的一個介面,讓客戶去詢問組件,問它是否支援某個介面,在經過多次的這種詢問之後,客戶對於組件的認識將越來越清晰;而我這篇文章和下一篇文章就是對這種詢問機制進行詳細的剖析和總結。
關於IUnknown
上面說到組件本身提供一個對自己查詢的介面,那麼這個介面是什麼呢?這就是大名鼎鼎的IUnknown介面了,IUnknown介面在Windows SDK的unknwn.h中定義,它的定義如下:
複製代碼 代碼如下:
interface IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void **ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
};
這裡的STDMETHODCALLTYPE表示調用方式,也就是windows API的__stdcall方式。可以看到,在IUnknown中定義了一個名為QueryInterface的函數。客戶可以調用QueryInterface來判斷組件是否支援某個特定的介面,而對於剩下的AddRef和Release兩個介面操作,我會在之後的文章中進行總結。
所有的COM介面都需要繼承IUnknown介面;因此,如果某個客戶擁有一個IUnknown介面的指標,它並不需要知道它所擁有的介面指標到底是指向什麼類型的,而只需要知道此介面可以用來查詢其它介面就行了。
由於所有的COM介面都首先繼承了IUnknown,再根據對之前的文章COM編程——介面的背後 的理解,我們可以知道每個介面的vtbl中的前三個函數都是QueryInterface,AddRef和Release。這就使得所有的COM介面都可以被當成IUnknown介面來處理。如果某個介面的vtbl中的前三個函數不是這三個,那麼它將不是一個COM介面。由於所有的介面都是從IUnknown繼承而來的,因此所有的介面都支援QueryInterface。所以,組件的任何一個介面都可以被客戶用來擷取它所支援的其他介面。由於所有的介面指標同時也將是IUnknown指標,客戶並不需要單獨維護一個代表組件的指標,它所關心的將僅僅是介面的指標。
既然,我們可以只用QueryInterface去詢問組件是否支援某個介面,但是,這一切都是基於獲得了IUnknown介面指標之後,才能進行的操作,那麼如何獲得一個指向組件的IUnknown介面指標呢?我們可以實現一個CreateInstance函數,它建立一個組件並返回一個IUnknown指標;對於客戶來說,可以調用CreateInstance獲得IUnknown指標,而不用使用new操作符了。在系統的總結了COM的所有基礎知識之後,我再說說系統提供的一個建立組件執行個體的API函數。
關於QueryInterface
IUnknown中包含一個名為QueryInterface的成員函數,客戶可以通過此函數來查詢某個組件是否支援某個特定的介面。若支援,QueryInterface將返回一個指向此介面的指標;否則傳回值將是一個錯誤碼;然後客戶可以接著查詢其它介面。
從QueryInterface函數的聲明中可以看出,QueryInterface有兩個參數,第一個參數標識客戶所需的介面,這個參數是一個介面標識符(IID)結構,在之後的文章中,我會總結有關IID的知識的;第二個參數用來存放所請求的介面的地址。QueryInterface返回的是一個HRESULT值,它是一個具有特定結構的32位值,之後我也會進行總結的;對於返回的HRESULT值,在實際開發中,需要使用SUCCEEDED宏或FAILED宏去進行判斷HRESULT值是表示成功還是失敗。
QueryInterface的簡單實現
總結了QueryInterface的簡單實現,說白了,就是簡單原廠模式的實現;上面也說了,就是根據客戶提供的IID介面標識符,然後獲得對應的介面的指標,返回對應的介面的指標;如果組件支援客戶指定的介面,那麼應返回S_OK以及相應的指標;若不支援,傳回值應是E_NOINTERFACE,並將相應的指標傳回值置成NULL。下面通過一個簡單的例子來說明QueryInterface的簡單實現:
比如有上述的一個結構;介面IX和IY都繼承自IUnknown介面,組件CA實現了IX和IY介面,那麼QueryInterface的實現應該像下面這樣:
複製代碼 代碼如下:
HRESULT __stdcall CA::QueryInterface(const IID &iid, void **ppv)
{
if (iid == IID_IUnknown)
{
*ppv = static_cast<IX *>(this);
}
else if (iid == IID_IX)
{
*ppv = static_cast<IX *>(this);
}
else if (iid == IID_IY)
{
*ppv = static_cast<IY *>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
static_cast<IUnknown *>(*ppv)->AddRef();
return S_OK;
}
QueryInterface的簡單使用
當我獲得了一個IUnknown指標以後,就可以調用對應的QueryInterface進行查詢了,如下:
複製代碼 代碼如下:
void Fod(IUnknown *pI)
{
IX *pIX = NULL;
// Ask for interface IX
HRESULT hr = pI->QueryInterface(IID_IX, (void **)&pIX);
// Check the return value
if (SUCCEEDED(hr))
{
// Use the interface
pIX->Fx();
}
}
完整的例子
上面說了那麼多了,現在提供一個完整的例子,將上面的各種理論知識都在實際代碼中進行了實踐,讓各位能更好的理解QueryInterface。(下載)。
總結
QueryInterface理解起來比較簡單,但是,它的理論知識還是必須要去掌握的,理論是一切的基礎,沒有理論作為支撐,任何實際的操作都不會那麼可靠和可信,所以,這篇文章總結的偏於理論多一些。由於QueryInterface部分的內容比較多,使用一篇文章無法總結的齊全,所以,之後我還會繼續總結關於QueryInterface的第二部分。