Tip
新式 QueryInterface 模式 - 使用 IID_PPV_ARGS 和智能指针。 原始 QueryInterface 调用容易出错(IID 和指针类型不匹配)。 新式 C++ 代码应使用类型安全的帮助程序:
#include <wrl/client.h> // Microsoft::WRL::ComPtr
Microsoft::WRL::ComPtr<IUnknown> unknown = /* ... */;
Microsoft::WRL::ComPtr<IPersistFile> persistFile;
// ✅ Best — ComPtr::As() handles QI + type safety + Release automatically
HRESULT hr = unknown.As(&persistFile);
// ✅ Good — IID_PPV_ARGS macro ensures IID matches the pointer type
hr = unknown->QueryInterface(IID_PPV_ARGS(&persistFile));
// ❌ Dangerous — IID and pointer type can mismatch silently
hr = unknown->QueryInterface(IID_IPersistFile, (void**)&persistFile);
C++/WinRT 等效项:
#include <winrt/base.h>
winrt::com_ptr<IUnknown> unknown = /* ... */;
auto persistFile = unknown.as<IPersistFile>(); // throws on failure
auto maybePF = unknown.try_as<IPersistFile>(); // returns nullptr on failure
关键规则: 绝不要在没有 QueryInterface 的情况下强制转换接口指针——COM 标识规则要求每个接口指针都必须通过 QI 或 CoCreateInstance 获取。 直接强制转换(static_cast、reinterpret_cast)会导致未定义行为。
在对象上具有指向接口的初始指针后,COM 有一种非常简单的机制,用于确定该对象是否支持另一个特定接口,如果是这样,则获取指向它的指针。 (有关获取对象上接口的初始指针的信息,请参阅获取指向对象的指针。此机制是 IUnknown 接口的 QueryInterface 方法。 如果对象支持请求的接口,该方法必须返回指向该接口的指针。 这允许对象通过对象支持的接口自由导航。 QueryInterface 将请求“是否支持给定合同?”与协商成功后该协定的高性能使用分开。
当客户端最初获取对对象的访问权限时,该客户端至少会收到 一个 IUnknown 接口指针(最基本的接口),通过它可以通过告诉对象何时使用对象完成并调用 QueryInterface 来控制对象的生存期。 客户端被编程为让其管理的每个对象执行某些操作,但 IUnknown 接口对这些操作没有函数。 相反,这些操作通过其他接口表示。 因此,客户端被设计为就这些接口与对象进行协商。 具体而言,客户端将调用 QueryInterface 来请求对象以获取客户端可以调用所需操作的接口。
由于对象实现 QueryInterface,因此它能够接受或拒绝请求。 如果对象接受客户端的请求, QueryInterface 将返回指向所请求接口的新指针到客户端。 通过该接口指针,客户端可以访问该接口的方法。 另一方面,如果对象拒绝客户端的请求, QueryInterface 将返回空指针(错误),并且客户端没有通过该指针调用所需函数。 在这种情况下,客户端必须妥善处理这种可能性。 例如,假设客户端具有指向对象上接口 A 的指针,并请求接口 B 和 C。假设该对象还支持接口 B,但不支持接口 C。结果是对象返回指向 B 的指针,并报告 C 不受支持。
关键点是,当对象拒绝对 QueryInterface 的调用时,客户端不可能要求该对象执行通过请求的接口表示的操作。 客户端必须具有接口指针才能调用该接口中的方法。 如果该对象拒绝提供所请求的指针,客户端就必须做好在没有该指针的情况下继续处理的准备:要么放弃对该对象执行原本打算进行的操作,要么尝试退而使用另一个接口,尽管该接口的功能可能较弱。 COM 功能的此功能与其他面向对象的系统相比非常有效,在该系统中,你不知道函数在调用该函数之前是否有效,即使这样,处理失败也不确定。 QueryInterface 提供了一种可靠且一致的方法来了解对象在尝试调用其方法之前是否支持接口。
QueryInterface 方法还提供了一种稳健且可靠的方式,使对象能够表明它不支持给定的契约。 也就是说,如果在对 QueryInterface 的调用中,询问一个“旧”对象是否支持“新”接口(例如,在旧对象发货后发明的接口),旧对象将可靠地,而不会造成崩溃,回答“否”。支持此功能的技术是分配 IID 的算法。 虽然这看似只是一个细微的问题,但它对系统的整体架构极其重要;而且令人惊讶的是,向遗留元素查询新功能的能力,是大多数其他对象架构所不具备的一项特性。
相关主题