一: 接口定义
任何一个接口都是继承于IUnknown接口。
客户同组件的交互都是通过一个接口完成的。在客户查询组件的其他接口时,也是通过接口完成的。这个接口就是Iunkown,它的定义包含在Win32 SDK中的UNKOWN.h头文件中。
interface IUnkown { virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) = 0; virtual ULONG __stdcall AddRef() = 0; virtual ULONG __stdcall Release() = 0; };
2. 获取IUnkown指针
有一个叫CreateInstance的函数,它可以建立一个组件并返回一个IUkown指针:
IUnkown* CreateInstance();
在此,客户不必再使用new操作符。
3. QueryInterface函数
客户可使用IUnkown中的成员函数QueryInterface查询组件是否支持某个特定的接口,如果支持,将返回一个指向此接口的指针;如果不支持,则返回一个错误代码。
HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
其中,第一个参数iid标志客户所需的接口,第二个参数ppv存放所请求接口的指针地址。
4. 使用QueryInterface
IUnkown* pI; //define a pointer for the interface IX* pIX = NULL; //ask for interface IX HRESULT hr = pI->QueryInterface(IID.IX, (void**) &pIX); //check return value if(SUCCEEDED(hr)) { //use interface pIX->fx(); }
在这段代码中,我们查询了pI是否支持由IID.IX所标识的接口。若成功返回,那么我们就可以使用它返回的指针了。
5. QueryInterface的实现
QueryInterface需要根据某个给定的IID返回指向相应接口的指针。如果组件支持客户指定的接口,那么返回S_OK及相应的指针,否则,返回E_NOINTERFACE并将相应的指针返回值设为NULL。
例如我们有类CA,及其接口的继承关系:
HRESULT __stdcall CA::QueryInterface(const IID& iid, void** ppv) { if(iid == IID.IUnkown) { //the client wants the IUnkown interface *ppv = static_cast<IX*>(this); } else if(iid == IDD.IX) { //the client wants the IX interface *ppv = static_cast<IX*>(this); } else if (iid == IDD.IY) { //the client wants the IY interface *ppv = static_cast<IY*>(this); } else { //we don't support the interface the client wants. //be sure to set the resulting pointer to NULL *ppv = NULL; return E_NOINTERFACE; } static_cast<IUnkown*>(*ppv)->AddRef(); return S_OK; }
在这里,QueryInterface使用一个简单的if-then-else语句实现。当然用其他任何一种可以将一种类型映射成另外一种类型的结构也是可以实现的。如可用数组、哈希表或树来实现。
6. QueryInterface的实现规则
(1) QueryInterface必须总是返回同一IUnkown指针
可以通过查询两个接口的IUnkown接口,并比较其返回值来确定这两个接口是否指向同一组件。
(2) 若客户曾经获取过某个接口,那么它将总能获取此接口
(3) 客户可以再次获取已经拥有的接口
(4) 客户可以返回到起始接口
(5) 若能够从某个接口获取某特定接口,那么可以从任意接口都将可以获取此接口