在 QueryInterface() 实现中调用 AddRef() 的正确方法
Proper way of calling AddRef() inside QueryInterface() implementation
我找到了一些 QueryInterface()
的实现模式:
// Inside some COM object implementation ...
virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppv)
{
*ppv = /* Find interface ... */
if (*ppv == nullptr)
return E_NOINTERFACE;
static_cast<IUnknown *>(*ppv)->AddRef(); // ###
return S_OK;
}
感兴趣的行是标有 // ###
评论的行。
在 IUnknown
static_cast
指针上调用 AddRef()
真的有必要吗?或者它只是无用的样板代码?
换句话说,一个简单的 AddRef()
调用(即 this->AddRef()
)就可以了吗?如果不是,为什么?
返回的接口不必是 class 实现 QueryInterface
的基础 class。
当然,您通常只有 一个 AddRef() 实现,因此您如何调用它并不重要。请注意代码使用 ppv
的方式是可能的灵感来源,它是无类型的 (void**),因此需要进行强制转换。也许撕掉会让你以不同的方式做这件事。
主要原因是分离接口指针(例如,对于很少使用的接口)和可聚合对象(或多或少等同于 COM 的 mixins)。
在这些情况下(撕裂,或要求聚合 IID 时的聚合器),ppv
不是指向同一引用计数 C++ 对象的接口指针。因此,如果您还想支持这些情况,那么该代码是必需的。
通过调用 this->AddRef
,您可能会获得一些简单性或类型安全性,但代价是不支持未由同一 C++ 对象显式实现的接口。
P.S.: 与大多数书籍和文档所说的相反,在我看来:
- 与继承或组合相比,聚合更类似于使用混合;
- 聚合实际上是(缓存的)分离接口指针的特例,而不是组合的特例。
这是我的思路:
- 当你继承时,你通常有机会重写(虚拟)方法,由于直接方法调用,聚合不会出现这种情况;当您使用组合时,您可能必须包装入站对象,以免内部对象将其身份泄露给给定对象(例如,内部对象可能将其自身传递给入站对象的某些方法),而聚合也意味着通过以下方式共享身份在可聚合对象上有两组
IUnknown
的方法,因此根本没有这个特殊问题;
- 分离对象有自己的生命周期,而可聚合对象与外部对象共享生命周期。否则,可能仅在需要时才创建任何一个,尽管聚合器通常会在创建可聚合对象后立即创建它们。
我找到了一些 QueryInterface()
的实现模式:
// Inside some COM object implementation ... virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppv) { *ppv = /* Find interface ... */ if (*ppv == nullptr) return E_NOINTERFACE; static_cast<IUnknown *>(*ppv)->AddRef(); // ### return S_OK; }
感兴趣的行是标有 // ###
评论的行。
在 IUnknown
static_cast
指针上调用 AddRef()
真的有必要吗?或者它只是无用的样板代码?
换句话说,一个简单的 AddRef()
调用(即 this->AddRef()
)就可以了吗?如果不是,为什么?
返回的接口不必是 class 实现 QueryInterface
的基础 class。
当然,您通常只有 一个 AddRef() 实现,因此您如何调用它并不重要。请注意代码使用 ppv
的方式是可能的灵感来源,它是无类型的 (void**),因此需要进行强制转换。也许撕掉会让你以不同的方式做这件事。
主要原因是分离接口指针(例如,对于很少使用的接口)和可聚合对象(或多或少等同于 COM 的 mixins)。
在这些情况下(撕裂,或要求聚合 IID 时的聚合器),ppv
不是指向同一引用计数 C++ 对象的接口指针。因此,如果您还想支持这些情况,那么该代码是必需的。
通过调用 this->AddRef
,您可能会获得一些简单性或类型安全性,但代价是不支持未由同一 C++ 对象显式实现的接口。
P.S.: 与大多数书籍和文档所说的相反,在我看来:
- 与继承或组合相比,聚合更类似于使用混合;
- 聚合实际上是(缓存的)分离接口指针的特例,而不是组合的特例。
这是我的思路:
- 当你继承时,你通常有机会重写(虚拟)方法,由于直接方法调用,聚合不会出现这种情况;当您使用组合时,您可能必须包装入站对象,以免内部对象将其身份泄露给给定对象(例如,内部对象可能将其自身传递给入站对象的某些方法),而聚合也意味着通过以下方式共享身份在可聚合对象上有两组
IUnknown
的方法,因此根本没有这个特殊问题; - 分离对象有自己的生命周期,而可聚合对象与外部对象共享生命周期。否则,可能仅在需要时才创建任何一个,尽管聚合器通常会在创建可聚合对象后立即创建它们。