【问题标题】:Why are COM pointer arguments cast as void instead of IUnknown?为什么 COM 指针参数转换为 void 而不是 IUnknown?
【发布时间】:2020-04-13 20:42:40
【问题描述】:

抱歉,如果这是一个愚蠢的问题,但我不清楚为什么 COM 指针参数通常被转换为 (void**) 而不是 (IUnknown**)。然后有时实际上使用了IUnknown 指针,例如IObjectWithSite::SetSite。谁能解释一下?

【问题讨论】:

  • TLDR:旧版。因为 COM 也适用于 C,而不仅仅是 C++。
  • @selbie 如果这真的是你理解的原因,你应该把它作为一个答案。
  • 声明多态接口指针的能力只存在于C++中。 COM 运行时 api(CoCreateInstance 等)是 C api,因此需要 C 类型。别无选择,只能作废*。实际上,在 C 中编写 OLE 是非常不切实际的,因此在 IObjectWithSite 等接口中使用 IUnknown* 是可行的。
  • @han:大部分是正确的,但是您将[in][out] 参数混为一谈。后者需要多态接口指针,因此void* 是给定 C ABI 的唯一选项。然而,在前一种情况下,接口需要IUnknown*,而不是某种多态(在语言级别的意义上)接口指针。这与实用性无关。这是关于正确性。

标签: c++ pointers winapi com iunknown


【解决方案1】:

在“get-type”接口方法(如IObjectWithSite::QueryInterfaceIObjectWithSite::GetSiteIMoniker::BindToObject 等)中,因为它不会改变任何东西,所以无论如何你都必须强制转换,除非你确实需要 IUnknown* 引用,但您已经拥有它,因为......您正在使用它(IUknown* 引用始终是每个 COM rules 的相同指针)。

IObjectWithSite::SetSite 是一种“set-type”方法,所以给你一个IUnknown* 参考更有意义。

CoCreateInstanceCoGetObject 等一些静态方法中可能更具争议性,我认为他们可以将IUnknown** 放在那里而不是void**,但那样的话,它们会有两种不同的风格。而且您将无法使用旧的 IID_PPV_ARGS 宏,它非常实用,建议作为一种编码实践来避免类型转换错误。

我建议你从 Don Box 那里获得一份权威的“Essential COM”副本,并阅读到第 60 页(至少 :-)。

【讨论】:

  • “无论如何你都必须投射” - 除了,你不能在 COM 中投射。如果您需要从一种接口类型移动到另一种接口类型,则需要调用QueryInterface“我认为他们可以将IUnknown** 放在那里而不是void** - 这是错误的。它假定接口继承总是使用语言级结构建模,该结构暗示指向这些接口的指针之间的关系。 COM 没有强制要求这个要求,像 C 这样的编程语言当然不能实现这个。
  • @IInspectable - 我的意思是你不会强制转换为 void** 所以 QI 编译。其余的,你想你想要什么。
  • @IInspectable - 当然,但有什么意义呢?如果您更喜欢它,我可以说“无论如何您都必须使用 C++”,但问题是关于 C++(并且 COM 确实起源于 C++),而且他们仍然可以使用 IUnknown*。事实上,他们有时会这样做:docs.microsoft.com/en-us/windows/win32/api/objidl/…
  • 与语言无关是 COM 的设计目标之一。为实现这一目标,ABI 的选项在实践中仅限于一个选项:C。如果有的话,COM 植根于 C,当然不是 C++。微软的 C++ 编译器决定生成兼容 COM 的 v-tables;即微软的 C++ 实现遵循 COM 规则,而不是相反。你还是错了:一个可以返回any接口类型的out-parameter,必须通过一个指向final类型的指针返回接口指针。您发现错误签名的事实并没有改变这一点。基本原理可以在我的回答中找到。
【解决方案2】:

我猜你说的是通过指针“返回”值的输出参数,例如:

IUnknown *u;
QueryInterface(IID_IUnknown, (void **)&u);

IDispatch *d;
QueryInterface(IID_IDispatch, (void **)&d);

注意:See here 如果您不熟悉使用指针传递来“返回”值的概念。


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 的调用都应该检查返回值,为简洁起见,我在这里省略了。

【讨论】:

  • void**IUnknown** 之间实际上有一个非常重要的区别,即所需的转换类型(在末尾的IFoo* foo = ... 行中)。使用void**static_castreinterpret_cast 就足够了,而使用IUnknown** 则需要dynamic_cast(这将使使用QueryInterface 变得毫无用处),或者不能保证返回指针实际指向有效的IUnknown(尤其是在对象的实现中使用多重和/或虚拟继承时)
  • @Bizzarrus 在这两种情况下你都必须使用reinterpret_cast (无论如何,这两种情况下都是未定义的行为,正如我的回答所涵盖的那样)。我同意使用void ** 可能不会那么混乱。我已经更新了那段
  • 对不起,我表达的方式很混乱。我的意思是说您在答案中编辑的内容,我的英语让我失望了._.
  • 我不清楚您所说的“不是有效的IUnknown* 值”是什么意思。所有 COM 接口都必须继承自 IUnknown
  • @JonathanPotter 在多重继承中,指针的值可能与指向其基数之一的指针的值不同。 IOW reinterpret_cast&lt;Base *&gt;(d)dynamic_cast&lt;Base *&gt;(d) 不同。
【解决方案3】:

重要提示

这与传统或便利无关。函数签名是 COM 基础允许它工作的结果。 必须按原样输入。如果您不想通读此答案,以下是重要的要点: C++ 中强制转换的道德等价物是在 COM 中调用 QueryInterface。只有在实现 COM 对象的 QueryInterface 时才允许在 COM 中使用 C++ 类型转换。

详情

COM 中期望void*(而不是IUnknown*)地址作为输出参数的函数签名可以返回任何 接口类型。如果在假设的实现中将其更改为 IUnknown**,那么使用 COM 将变得不切实际或完全不可能。

让我们进行 2 个思想实验,从使 COM 无法使用的一个开始:

假设CoCreateInstance 将返回IUnknown*,而不是通过void* 请求的真实接口。在这种情况下,客户端必须立即在返回的IUnknown* 上调用QueryInterface 以接收他们请求的接口指针1。这不实用2

这会立即导致无法解决的实验。假设QueryInterface 返回了IUnknown*。要获得真正的接口指针,客户端需要调用QueryInterface。但这只会返回IUnknown*!此时,COM 的可消耗接口表面已折叠为单个接口,IUnknown

每当 COM 返回指向接口的指针时,它必须返回指向最终类型的指针。唯一匹配所有接口指针的编程语言类型确实是void*3 这应该可以解释为什么输出参数需要输入void** 而不是IUnknown**

另一方面,对于IObjectWithSite::SetSiteIUnknown* 是一个输入。该接口仍然接受any COM 接口,但它需要作为指向(身份可比)IUnknown 接口的指针传递。


1COM 不要求实现特定的对象布局。相反,它将对接口指针的请求委托给各自的QueryInterface 实现。

2即使忽略在IUnknown 上调用Release() 的直接需求,以将增加的引用计数作为QI 调用的一部分。

3来自The Component Object Model: “COM 的唯一语言要求是生成的代码可以创建指针结构,并且可以显式或隐式地创建指针结构。 , 通过指针调用函数。” 故意不要求接口继承使用语言级结构来实现。即使需要IFoo 来实现IUnknown,在像C 这样的编程语言中,IFoo*IUnknown* 之间也没有关系。

【讨论】:

    猜你喜欢
    • 2014-10-22
    • 2019-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-07
    • 1970-01-01
    • 2011-07-31
    相关资源
    最近更新 更多