如果这是一个愚蠢的问题,我深表歉意,但我不清楚为什么 COM 指针参数通常被转换为(void**)
而不是(IUnknown**)
. 然后有时IUnknown
实际上会使用指针,例如 with IObjectWithSite::SetSite
。谁能解释一下?
3 回答
在“get-type”接口方法(如IObjectWithSite::QueryInterface
, IObjectWithSite::GetSite
, IMoniker::BindToObject
, 等)中,因为它不会改变任何东西,所以无论如何你都必须强制转换,除非你确实需要IUnknown*
引用,但你已经有了它,因为 . ..您正在使用它(根据COM 规则IUknown*
,引用始终是相同的指针)。
IObjectWithSite::SetSite
是一种“set-type”的方法,所以给你一个IUnknown*
参考更有意义。
在像CoCreateInstance或CoGetObject这样的静态方法中,这可能更具争议性, 我认为他们可以把它们放在IUnknown**
那里而不是,void**
但是它们会有两种不同的风格。而且您将无法使用旧的IID_PPV_ARGS宏,它非常实用,建议作为编码实践来避免类型转换错误。
我建议您从 Don Box 获取权威的“Essential COM”副本,并阅读到第 60 页(至少 :-)。
我猜您正在谈论通过指针“返回”值的输出参数,例如:
IUnknown *u;
QueryInterface(IID_IUnknown, (void **)&u);
IDispatch *d;
QueryInterface(IID_IDispatch, (void **)&d);
注意:如果您不熟悉使用指针传递来“返回”值的概念,请参阅此处。
的原型QueryInterface
是:
HRESULT QueryInterface(REFIID iid, void **ptr);
该参数&u
已经具有类型IUnknown **
(因此没有理由将其转换为它已经存在的类型,正如您似乎建议的那样)。强制转换是将类型更改为函数参数的预期类型。
在标准 C++ 中,上面的代码实际上是未定义的行为(严格的别名冲突——你不能假装指针指向不同的类型对象,而不是实际指向的对象)。这个函数的正确用法是:
void *temp;
QueryInterface(IID_IFoo, &temp);
IFoo *foo = static_cast<IFoo *>(temp);
但是,Microsoft 编译器支持具有 C 样式转换的版本。它对内存位置进行相同的更改,就好像接口指针被声明为void *
而不是它的真实类型一样。
为什么QueryInterface
取void **
而不取IUnknown **
?好吧,如果您特别要求避免强制转换,那会很好IID_IUnknown
,但是任何其他接口都需要 C 风格的强制转换。它可能会引起混淆,因为(对于其他接口)返回的指针不一定是有效值IUnknown *
。
在 C++ 编程中,您可以(也许应该)使用执行所有正确类型操作的模板包装类。Windows API 调用与 C 兼容,因此它们不能包含强类型泛型。
注意。所有调用都QueryInterface
应该检查返回值,为简洁起见,我在这里省略了。
重要的外卖
这与传统或便利无关。函数签名是 COM 基础允许它工作的结果。它们必须按原样键入。如果您不想通读此答案,以下是重要的要点: C++ 中强制转换的道德等价物是QueryInterface
COM 中的调用。只有在实现 COM 对象的QueryInterface
.
细节
COM 中期望 a void*
(而不是 a IUnknown*
)的地址作为输出参数的函数签名可以返回任何接口类型。如果IUnknown**
在假设的实现中将其更改为 an,则使用 COM 将变得不切实际或完全不可能。
让我们进行 2 个思想实验,从使 COM 无法使用的那个开始:
让我们假设CoCreateInstance将返回 aIUnknown*
而不是通过 a 请求的真实接口void*
。在这种情况下,客户端必须立即调用QueryInterface
返回IUnknown*
的接口以接收他们要求的接口指针1。这不切实际2。
这立即导致无法解决的实验。让我们假设QueryInterface
返回一个IUnknown*
. 要获得真正的接口指针,客户端需要调用QueryInterface
. 但这只会返回一个IUnknown*
!此时,COM 的可消耗接口表面已折叠为单个接口,IUnknown
.
每当 COM 返回指向接口的指针时,它必须返回指向最终类型的指针。唯一匹配所有接口指针的编程语言类型确实是void*
. 3
这应该解释为什么需要输入输出参数void**
而不是IUnknown**
.
另一方面,对于IObjectWithSite::SetSiteIUnknown*
,是一个input。该接口仍然接受任何COM 接口,但它需要作为指向 (identity-comparable)IUnknown
接口的指针传递。
1 COM 不要求实现特定的对象布局。相反,它将对接口指针的请求委托给各自的QueryInterface
实现。
2 即使忽略了调用的即时需要,以Release()
将IUnknown
碰撞的引用计数作为QI
调用的一部分。
3 来自组件对象模型: “COM 的唯一语言要求是,代码是用一种可以创建指针结构的语言生成的,并且可以显式或隐式地通过指针调用函数。” 故意不要求使用语言级结构来实现接口继承。即使在IFoo
需要实现时,与C 这样的编程语言IUnknown
之间也没有关系。IFoo*
IUnknown*