何时需要/适合在COM互操作中使用InAttribute和OutAttribute?

19

我正尝试整理我们在各个项目中散布着的COM互操作定义,并将它们收集到一个单一的、确定无误的位置,以便整个开发团队可以从中受益。这一努力的一部分涉及清理多年积累的定义。

其中一些是从其他源代码借用来的,有些是直接从pinvoke.net复制而来的,还有一些看起来是直接从SDK头文件翻译过来的。我注意到的一件事是,使用各种封送属性的时机没有一致性,(即使在pinvoke.net的示例中也很随意)。问题的一部分在于,我不认为任何人(包括我自己)完全理解何时需要或不需要使用各种属性,或者它们实际上做了什么。到目前为止,正确地处理它们似乎是猜测和随机更改的组合,直到COM异常停止出现,但我更希望翻译是正确的,因为有人真正查看并声明了它们。

因此,我从[In][Out]开始。我知道这两个属性的概念是什么:它们通知封送程序数据必须走的方向。例如,我假设封送程序不会将[In]数据复制回调用者,或知道需要在调用方释放[Out]数据等。我不知道的是:

  1. 什么时候必须使用这些属性?也就是说,默认的封送行为何时是错误的,以至于这些属性可以使其正确?
  2. 什么时候使用这些属性是安全的?也就是说,指定应该已经是默认行为的内容会改变封送程序的工作方式吗?
  3. 使用这些属性什么时候是危险的?我假设明确标记输出参数[In]是不好的,但标记输入参数[In, Out]实际上会破坏什么吗?

因此,考虑到一个假设的COM接口方法,其IDL代码如下:

HRESULT Foo(
    [in] ULONG a,
    [out] ULONG * b
    [in, out] ULONG * c);

以下是可能的翻译:

void Foo(
  uint cb,
  out uint b,
  ref uint c);

void Foo(
  uint cb,
  [Out] out uint b,
  [In, Out] ref uint c);

void Foo(
  [In] uint cb,
  [Out] out uint b,
  [In, Out] ref uint c);

这三者之间是否存在功能上的差异?除了技术正确性以外,它们中有哪一个被认为比其他人更好?


2
在这里要小心,你正在比较不同的事物。IDL文件中的[in]和[out]属性仅在midl.exe生成代理/存根代码时使用。而这些代码仅在需要在公寓或进程之间进行COM调用的情况下使用。.NET [In]和[Out]属性仅由pinvoke marshaller使用。它在非常不同的运行时环境中运行,始终在进程内部,并且不考虑公寓。它们的目的当然是相同的,即优化调用。 - Hans Passant
1
是的,我(认为我)理解了这部分内容。对于我的目的,在将IDL翻译成C#时,我只使用IDL属性作为方法“应该”如何行为的提示。换句话说,在IDL中的[out]属性意味着接口编写者打算让调用者返回带有存储在其中的值的参数,因此我需要确保C#代码以同样的方式运行。我不太清楚.NET marshaller的默认行为以及何时需要告诉它该做什么,而不是让它自己做正确的事情。 - Michael Edenfield
2
在IDL中,它们是必需的,因为当使用指针时,数据流方向完全不清楚。但在C#中使用refout关键字则不是问题。在C#中,99%的情况只需省略属性即可。如果您实际上正在用C#替换COM代码,则为100%必需。 - Hans Passant
1个回答

18
不是必须在您自己的代码中应用这些属性。它们都是可选的,并且根据参数类型自动应用于您的代码。基本上,它们有以下C#关键字等效项:
- 您默认获得[In]。 - ref关键字可以获得[In, Out]。 - out关键字可以获得[Out]
所有这些都在一个漂亮的表格here中记录。
只有当您想要更改这些隐含的语义或更改编组器的默认行为时,才需要使用它们。例如,您可以仅使用[In]属性标记声明为ref的参数,以抑制编组的“out”部分。
话虽如此,我发现自己使用它们是因为它们是使代码自我说明的非常简单的方法。

我喜欢自我记录的想法!另外补充一点:对于布尔值和字符串,使用MarshalAs更加相关,请也注意这些... - eFloh
啊,这解释了我在pinvoke.net上经常看到的一些东西!例如,如果一个方法需要一个REFIID参数,通常会是“ref Guid”。但是如果参数在IDL中被指定为[in],我可以将其指定为“[In] ref Guid refiid”,因为我不需要它,所以它不会打扰将Guid结构返回给我,对吗? - Michael Edenfield
@Michael:是的,没错。你将无法看到对结构所做的任何修改,但如果你不需要它们,那么标记为[In] ref就可以了。话虽如此,这绝对是一种微观优化,可能并不值得过多地纠结于正确性。 - Cody Gray
这似乎不正确。最近我遇到了这样一种情况:使用 ref 将一个类传递给本地方法时,该类无法被本地方法更新;但是,如果在方法声明中使用 [In, Out],它就可以工作。 - Herohtar
在编组类的页面的底部,即 SysTime 示例 中: "参数必须声明为 InAttributeOutAttribute 属性,因为类是引用类型,它们默认作为 In 参数传递。为了使调用者接收结果,必须显式应用这些方向性属性。" - Herohtar
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接