QueryInterface
定义
QueryInterface是 IUnknown 接口中声明的方法之一,另外两个是用于增减引用计数的 AddRef 与 Release。[3] 它在 C++ 中的形式为 HRESULT QueryInterface(REFIID riid, void **ppvObject),第 1 个参数是所需接口的接口标识符 IID,第 2 个参数用于接收查询到的接口指针。[1]
IUnknown 在 unknwn.h 中定义,3 个方法均声明为纯虚函数,其自身的接口标识符为 {00000000-0000-0000-C000-000000000046},因此不能直接实例化,必须由其他接口继承。[2] 查询成功时方法返回 S_OK,失败时返回 E_NOINTERFACE,并把输出指针置为空;若输出指针本身为空,则返回 E_POINTER。[1]
原理
COM 对象以虚函数表(vtable)对外暴露方法,而所有接口都派生自 IUnknown,故每个接口的 vtable 前 3 项依次是 QueryInterface、AddRef 和 Release,这使得任何接口指针同时也是一个 IUnknown 指针。[2] 客户端只持有一个 IUnknown 指针,就能通过 QueryInterface 逐个取得对象所支持的其他接口,而无需另外维护一个指向组件本身的指针。[4]
典型的实现是在函数内逐项比较传入的 IID:命中某个接口就把 this 指针转换为该接口类型并写入输出参数,随后调用 AddRef 增加引用计数并返回 S_OK;所有分支都不命中时把输出指针置空并返回 E_NOINTERFACE。[4] 不同组件的实现差别仅在于各自需要比对的 IID 清单不同,凡是组件支持的接口都必须在该清单中出现。[4]
由于 C++ 多重继承会改变基类指针的地址,把 this 直接转换为 IUnknown* 可能产生二义性,实现时通常先转换为某个具体接口指针再转换为 IUnknown,并保证每次查询 IID_IUnknown 得到同一地址。[5] 微软给出的实现规则包括:对象必须具有同一性,即用 IID_IUnknown 查询总返回相同的物理指针值;对象实例支持的接口集合必须是静态的,某次查询成功后不得再次失败,反之亦然;并且从任意一个接口出发都应能查询到对象上的其他任意接口,即具备自反性、对称性与可传递性。[6]
sequenceDiagram
participant C as 客户端
participant U as IUnknown
participant O as COM 对象
C->>U: QueryInterface(IID_IX, &pIX)
U->>O: 比对 IID
alt 支持该接口
O-->>C: pIX 指针 + AddRef,返回 S_OK
else 不支持
O-->>C: pIX = NULL,返回 E_NOINTERFACE
end
发展历程
COM 由微软于 1993 年提出,作为 OLE 2.0 的基础,用以解决 OLE 1.0 结构过于复杂的问题。[7] 微软规定每个 COM 组件至少要实现 IUnknown,由此确立了 QueryInterface、AddRef、Release 作为所有组件共同具备的 3 项能力。[7]
1996 年发布的 DCOM 将 COM 扩展到网络环境,接口查询经代理与存根在跨进程、跨机器调用中被转发;代理通常会缓存查询结果,以避免每次查询都产生一次网络往返。[8] 2000 年随 Windows 2000 出现的 COM+ 在 COM 之上整合了事务等服务;此后 .NET 成为微软新的组件技术,通过运行时可调用包装器等机制与既有 COM 组件互操作,COM 及其接口查询机制仍保留在 Windows 中并被广泛使用。[9]
Windows 运行时(WinRT)同样是基于 COM 的 API,属于 COM 的一种增强变体;纳米 COM(Nano-COM)则只保留 COM 的应用程序二进制接口部分,被用作 DirectX、Direct3D 与 DirectML 的基础 ABI 技术,其中 IUnknown 仍是对象引用的基础类型。[9]
应用
在 OLE 自动化中,客户端可先通过 QueryInterface 取得 IDispatch 接口,再借助其 Invoke 方法以名称或方法编号调用对象成员,这是后期绑定调用的常见路径。[10] 组件聚合场景下,被聚合组件需要把 IUnknown 的调用委托给外部组件,使聚合体对客户端只呈现一个 IUnknown 接口,QueryInterface 的委派实现因此成为聚合正确工作的前提。[4]
在 .NET 与 COM 互操作中,ICustomQueryInterface 接口允许开发者提供自定义的托管 QueryInterface 实现,为除 IID_IUnknown 以外的特定接口标识符返回特定接口,该接口已被标记为在后续版本中可能不再提供。[11] 在工业控制领域,IEC 61131-3 的扩展运算符 __QUERYINTERFACE 也以类似方式在运行时把一种接口引用转换为另一种类型,并返回表示转换是否成功的布尔结果。[12]
系统监控类工具同样会利用该机制:由于一个对象可能拥有多个 vtable,可以通过已知接口调用 QueryInterface 定位其他 vtable,但这样做会带来相当可观的处理开销。[10]
局限
QueryInterface 只能回答“是否支持某个接口”,无法表达接口的版本差异或能力强弱;接口一经发布即不应修改,客户端若需新功能只能定义新接口并另行查询。[13] 查询失败时客户端必须自行处理该情形,要么放弃操作,要么退回到功能较弱但可用的接口。[14]
该机制不提供访问控制能力。微软明确指出 QueryInterface 的实现绝不能检查访问控制列表,原因之一是 COM 要求支持某接口的对象在查询时必定返回成功,而且即便检查也无法带来实际安全性,因为能访问该接口的客户端可以直接把指针转交给他人。[1] 此外,接口集合必须是静态的,实现者不能在运行中动态增删对象所支持的接口。[15]
在使用层面,调用 QueryInterface 可能开销较高,代码中常通过缓存已取得的接口指针来减少重复查询,而先调用 QueryInterface 再立即调用 Release 以获取弱引用的做法不被接受,这样得到的指针无效。[16]
参见
-
IUnknown —— QueryInterface 所在的 COM 基础接口,另含 AddRef 与 Release 两个方法。
-
组件对象模型 —— QueryInterface 所属的二进制组件标准,定义了接口与对象标识规则。
-
IDispatch —— 派生自 IUnknown 的自动化接口,可通过 QueryInterface 取得后用于后期绑定调用。
-
DCOM —— 把 COM 扩展到网络的技术,接口查询需经代理与存根转发。
-
ActiveX —— 基于 COM 的控件技术,其控件同样通过 QueryInterface 暴露接口。
-
引用计数 —— 与 QueryInterface 配合使用的对象生命周期管理方式。
参考资料
- IUnknown::QueryInterface (REFIID,void) [引用日期2026-10-29]
- IUnknown [引用日期2026-10-29]
- IUnknown [引用日期2026-10-29]
- IUnknown 的工作原理 [引用日期2026-10-29]
- QueryInterface详解 COM [引用日期2026-10-29]
- rules-for-implementing-queryinterface [引用日期2026-10-29]
- 组件对象模型_百度百科 [引用日期2026-10-29]
- 6zzy7zky(v=vs [引用日期2026-10-29]
- Component Object Model [引用日期2026-10-29]
- 给定了类 id 和接口 id 之后,则可创建对象实例 [引用日期2026-10-29]
- ICustomQueryInterface 介面 (System.Runtime.InteropServices) [引用日期2026-10-29]
- Operator '__QUERYINTERFACE' [引用日期2026-10-29]
- PerssonE-Shadows(PDF) [引用日期2026-10-29]
- QueryInterface 在 物件中巡覽 [引用日期2026-10-29]
- title: Rules for Implementing QueryInterface [引用日期2026-10-29]
- github.com 上的网页 [引用日期2026-10-29]