我们已经知道了一个对象可以实现一个或多个接口,在上面对话框的案例中,Common Item Dialog 对象实现了 IFileOpenDialog 接口,这个接口定义显示和获取选择文件的基本方法。

为了提供更高级的功能,该对象还定义了一个名字叫做 IFileDialogCustomize 的接口。程序可以使用这个接口自定义对话框的行为,或者为对话框添加新的 UI 控件。

回忆每个 COM 接口 的继承关系,每个接口都会直接或者间接的继承 IUnknown 接口,下面是 Common Item Dialog 对象的继承关系图:

继承关系图

如图所示,IFileOpenDialog 继承了 IFileDialog 接口,而后者是从 IModalWindow 接口继承而来。从 IFileOpenDialog 到 IModalWindow 的继承链中,接口越来越抽象,提供的功能越来越泛化,最终 IModalWindow 继承 IUnknown 接口。Common Item Dialog 对象还包括 IFileDialogCustomize 接口,该接口在一条单独的继承链中。

现在问题来了,如果你有一个 IFileOpenDialog 接口指针,你如何获取 IFileDialogCustomize 接口对象?

如何获取接口

如果简单的将 IFileOpenDialog 指针转换为 IFileDialogCustomize 指针是不可行的。没有一种简单可靠的、可以直接实现这种跨越继承关系的转换方法。这里面没有类似运行时类型信息(RTTI)的东西帮助转换,并且该功能高度依赖于语言特性。

COM 组件 的解决方案是通过询问对象来获取你想要的 IFileDialogCustomize 指针对象。通过首次获取的接口指针进入对象的继承管道。具体来说就是通过首次获取的接口调用 IUnknown::QueryInterface 方法查询 IFileDialogCustomize 指针。你可以将 QueryInterface 接口看作是类似 C++ 语言的 dynamic_cast 方法,只不过该方法独立于语言之上。

QueryInterface 接口声明如下:

HRESULT QueryInterface(REFIID riid, void **ppvObject);

根据 CoCreateInstance 接口的理论,你可以大致猜测到 QueryInterface 接口的工作原理:

  • riid参数代表着你想要询问的接口标识(GUID),这个参数的类型 REFID 是 const GUID& 的别名。在这里类的标识(CLSID)不是必须的,因为对象已经被创建了,所以这里只需要接口标识即可。

  • ppvObject 参数用来存储想要获取的接口指针。这个数据类型是 void**,具体原因和 CoCreateInstance 接口中的类似:“QueryInterface 可以被任何 COM 接口使用,所以参数不能是强类型”。

下面是通过 QueryInterface 函数查询 IFileDIalogCustomize 接口的案例:

hr = pFileOpen->QueryInterface(IID_IFileDialogCustomize,
    reinterpret_cast<void**>(&pCustom));
if (SUCCEEDED(hr))
{
    // Use the interface. (Not shown.)
    // ...

    pCustom->Release();
}
else
{
    // Handle the error.
}

和前面一样,别忘记检测函数的返回值,如果函数调用成功,一定不要忘记调用 Release 函数减少引用计数。

求关注