为什么将 COM 指针参数强制转换为 void 而不是 IUnknown?
Why are COM pointer arguments cast as void instead of IUnknown?
抱歉,这是一个愚蠢的问题,但我不清楚为什么 COM 指针参数通常被转换为 (void**)
而不是 (IUnknown**)
。然后有时实际上使用 IUnknown
指针,例如 IObjectWithSite::SetSite
。谁能解释一下?
我猜你是在谈论通过指针 "return" 值的输出参数,例如:
IUnknown *u;
QueryInterface(IID_IUnknown, (void **)&u);
IDispatch *d;
QueryInterface(IID_IDispatch, (void **)&d);
注意: 如果您不熟悉使用指针传递到 "return" 值的概念。
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 风格的强制转换。这可能会引起混淆,因为(对于其他接口)returned 指针不一定是有效的 IUnknown *
值。
在 C++ 编程中,您可以(也许应该)使用模板包装器 类 来执行所有正确的类型操作。 Windows API 调用与 C 兼容,因此它们不能包含强类型泛型。
注意。所有对 QueryInterface
的调用都应检查 return 值,为了简洁起见,我在这里省略了它。
在 "get-type" 接口方法中(如 IObjectWithSite::QueryInterface
、IObjectWithSite::GetSite
、IMoniker::BindToObject
等),因为它不会改变任何东西,所以你会无论如何都必须进行转换,除非你确实需要一个 IUnknown*
引用,但你已经有了它,因为......你正在使用它(IUknown*
引用始终是相同的指针每个 COM rules).
IObjectWithSite::SetSite
是一个"set-type"方法,所以给大家一个IUnknown*
参考更有意义。
在某些静态方法(如 CoCreateInstance or CoGetObject I think they could have put IUnknown**
there instead of void**
but then, they would have two different styles. And you wouldn't be able to use the old IID_PPV_ARGS 宏)中可能更具争议性,它非常实用,建议将其作为编码实践以避免类型转换错误。
我建议您从 Don Box 获得权威 "Essential COM" 的副本,并阅读第 60 页(至少 :-)。
重要提示
这与传统或方便无关。函数签名是 COM 基础知识的结果,以允许它工作。 要求 按原样输入。如果您不想通读这个答案,这里有重要的要点: C++ 中强制转换的道德等价物是对 COM 中 QueryInterface
的调用。唯一允许在 COM 中使用 C++ 强制转换的情况是在实现 COM 对象的 QueryInterface
.
时
详情
COM 中期望 void*
(相对于 IUnknown*
)地址作为输出参数的函数签名可以 return any 接口类型。如果在假设的实现中将其更改为 IUnknown**
,则使用 COM 要么不切实际,要么完全不可能。
让我们进行 2 个思想实验,从一个会使 COM 无法使用的实验开始:
让我们假设 CoCreateInstance 是 return 一个 IUnknown*
而不是通过 void*
请求的真实接口。在这种情况下,客户端必须立即在 returned IUnknown*
上调用 QueryInterface
以接收他们请求的接口指针 1。这不实用2.
这立即导致无法解决的实验。假设 QueryInterface
return 编辑了一个 IUnknown*
。要获得真正的接口指针,客户端需要调用 QueryInterface
。但这只有 return 一个 IUnknown*
!到那时,COM 的可消费接口表面已经折叠成一个单一的接口,IUnknown
。
每当 COM return 指向接口的指针时,它必须 return 指向最终类型的指针。唯一匹配所有接口指针的编程语言类型确实是 void*
.3
这应该可以解释为什么输出参数需要输入 void**
而不是 IUnknown**
.
对于 IObjectWithSite::SetSite,另一方面,IUnknown*
是一个 输入 。该接口仍然接受 any COM 接口,但它需要作为指向(身份可比)IUnknown
接口的指针传递。
1 COM 不强制执行特定的对象布局。相反,它将对接口指针的请求委托给各自的 QueryInterface
实现。
2 即使忽略立即需要在 IUnknown
上调用 Release()
以将增加的引用计数作为一部分考虑在内QI
电话。
3 来自The Component Object Model: "The only language requirement for COM is that code is generated in a language that can create structures of pointers and, either explicitly or implicitly, call functions through pointers." 故意没有要求,就是接口继承可以使用语言级别的结构来实现。即使需要IFoo
来实现IUnknown
,在像C这样的编程语言中IFoo*
和IUnknown*
之间也没有关系。
抱歉,这是一个愚蠢的问题,但我不清楚为什么 COM 指针参数通常被转换为 (void**)
而不是 (IUnknown**)
。然后有时实际上使用 IUnknown
指针,例如 IObjectWithSite::SetSite
。谁能解释一下?
我猜你是在谈论通过指针 "return" 值的输出参数,例如:
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 风格的强制转换。这可能会引起混淆,因为(对于其他接口)returned 指针不一定是有效的 IUnknown *
值。
在 C++ 编程中,您可以(也许应该)使用模板包装器 类 来执行所有正确的类型操作。 Windows API 调用与 C 兼容,因此它们不能包含强类型泛型。
注意。所有对 QueryInterface
的调用都应检查 return 值,为了简洁起见,我在这里省略了它。
在 "get-type" 接口方法中(如 IObjectWithSite::QueryInterface
、IObjectWithSite::GetSite
、IMoniker::BindToObject
等),因为它不会改变任何东西,所以你会无论如何都必须进行转换,除非你确实需要一个 IUnknown*
引用,但你已经有了它,因为......你正在使用它(IUknown*
引用始终是相同的指针每个 COM rules).
IObjectWithSite::SetSite
是一个"set-type"方法,所以给大家一个IUnknown*
参考更有意义。
在某些静态方法(如 CoCreateInstance or CoGetObject I think they could have put IUnknown**
there instead of void**
but then, they would have two different styles. And you wouldn't be able to use the old IID_PPV_ARGS 宏)中可能更具争议性,它非常实用,建议将其作为编码实践以避免类型转换错误。
我建议您从 Don Box 获得权威 "Essential COM" 的副本,并阅读第 60 页(至少 :-)。
重要提示
这与传统或方便无关。函数签名是 COM 基础知识的结果,以允许它工作。 要求 按原样输入。如果您不想通读这个答案,这里有重要的要点: C++ 中强制转换的道德等价物是对 COM 中 QueryInterface
的调用。唯一允许在 COM 中使用 C++ 强制转换的情况是在实现 COM 对象的 QueryInterface
.
详情
COM 中期望 void*
(相对于 IUnknown*
)地址作为输出参数的函数签名可以 return any 接口类型。如果在假设的实现中将其更改为 IUnknown**
,则使用 COM 要么不切实际,要么完全不可能。
让我们进行 2 个思想实验,从一个会使 COM 无法使用的实验开始:
让我们假设 CoCreateInstance 是 return 一个 IUnknown*
而不是通过 void*
请求的真实接口。在这种情况下,客户端必须立即在 returned IUnknown*
上调用 QueryInterface
以接收他们请求的接口指针 1。这不实用2.
这立即导致无法解决的实验。假设 QueryInterface
return 编辑了一个 IUnknown*
。要获得真正的接口指针,客户端需要调用 QueryInterface
。但这只有 return 一个 IUnknown*
!到那时,COM 的可消费接口表面已经折叠成一个单一的接口,IUnknown
。
每当 COM return 指向接口的指针时,它必须 return 指向最终类型的指针。唯一匹配所有接口指针的编程语言类型确实是 void*
.3
这应该可以解释为什么输出参数需要输入 void**
而不是 IUnknown**
.
对于 IObjectWithSite::SetSite,另一方面,IUnknown*
是一个 输入 。该接口仍然接受 any COM 接口,但它需要作为指向(身份可比)IUnknown
接口的指针传递。
1 COM 不强制执行特定的对象布局。相反,它将对接口指针的请求委托给各自的 QueryInterface
实现。
2 即使忽略立即需要在 IUnknown
上调用 Release()
以将增加的引用计数作为一部分考虑在内QI
电话。
3 来自The Component Object Model: "The only language requirement for COM is that code is generated in a language that can create structures of pointers and, either explicitly or implicitly, call functions through pointers." 故意没有要求,就是接口继承可以使用语言级别的结构来实现。即使需要IFoo
来实现IUnknown
,在像C这样的编程语言中IFoo*
和IUnknown*
之间也没有关系。