在C#中使用PInvoke时,[In, Out]与ref之间有什么区别?

24

使用 [In, Out] 和仅使用 ref 在从 C# 到 C++ 传递参数时有区别吗?

我找到了一些不同的 SO 帖子,以及一些来自 MSDN 的东西,它们接近我的问题但并没有完全回答。我猜我可以像使用 [In, Out] 一样安全地使用 ref,并且编组器不会有任何不同的操作。我的担忧是它会有所不同,而且 C++ 不会对我的 C# 结构体传递感到满意。我在工作的代码库中看到了两种情况...

以下是我找到并一直阅读的帖子:

P/Invoke [In, Out] 属性对于数组编组是否是可选的? 这让我想使用 [In, Out]。

这三篇文章让我认为我应该使用[In, Out],但我可以使用ref代替,并且它将具有相同的机器代码。这让我觉得我是错误的 - 因此在这里提问。


您能给一个具体的例子吗?您想了解[In, Out] ref int fooref int foo之间的区别吗?就目前而言,您的问题缺乏细节,我认为您需要一个(或多个)具体的例子。 - David Heffernan
类似这样的东西:[DllImport("MyDll.dll", CallingConvention = CallingConvention.Cdecl)] private static extern bool myFunction([In, Out] SomeStruct[] aStruct; 在这里使用 [In, Out] 相当于在这里使用 ref。 - PerryC
还有,你刚刚将 [In, Out] 和 ref 结合起来了吗? - PerryC
不,它们不是相同的。 - David Heffernan
1个回答

33

refout 的使用并非随意。如果本地代码需要传递引用(指针),那么如果参数类型是值类型,则必须使用这些关键字,以便Jitter知道生成对该值的指针。如果参数类型是引用类型(类别),则必须省略它们,因为对象在底层已经是指针。

[In] 和 [Out] 属性是解决有关指针模棱两可的必要手段,它们不指定数据流。[In] 总是被PInvoke marshaller隐含,因此不需要显式声明。但是,如果您希望看到本地代码对结构或类成员所做的任何更改反馈到您的代码中,则必须使用[Out]。 PInvoke marshaller避免自动复制回来以避免额外开销。

另一个奇怪的地方是,[Out] 不经常需要。当值是 "可平整化" 时发生了这种情况,这是一个昂贵的词语,意思是托管值或对象布局与本机布局相同。然后,PInvoke marshaller 可以采取捷径,将对象固定并传递指向托管对象存储的指针。那么您将不可避免地看到更改,因为本机代码直接修改托管对象。
一般情况下,这是您强烈追求的东西,非常高效。通过给类型添加 [StructLayout(LayoutKind.Sequential)] 属性来帮助您,它抑制了 CLR 用来重新排列字段以获得最小对象的优化。通过仅使用简单值类型或固定大小缓冲区的字段,尽管您通常没有这个选择。永远不要使用 bool,而要使用 byte。没有简单的方法可以找出类型是否可平整化,除非它不能正常工作,或者使用调试器并比较指针值。
只需明确并始终在期望更新字段时使用 [Out] 即可。如果不需要,它不会产生任何费用。而且它是自我记录的。如果本机代码的架构发生变化,您仍然可以放心它仍然可以正常工作。

我相当确定我的对象不是可平凡的(虽然我不确定)。如果我有一些目前是[In,Out]的东西,将其更改为只使用[Out]会使代码更清晰并且行为方式相同吗?或者保留[In,Out]属性以显示双向意图是否更清晰?谢谢您的回答。 - PerryC
2
如果意图是双向的,请明确描述并使用 [In, Out]。 - Hans Passant
如果该值是可平铺的且不是引用类型(在这种情况下,是 C# 中的结构体),那么在结构体是顺序的且仅包含基元类型(int、float、double)的情况下,使用 ref 而不是使用 [In, Out] 是否可行?这是否与使用 [In, Out] 到 marshaller 相同,因为在任一情况下它都会将对象固定在内存中? - PerryC
3
你没有理解答案,使用ref不是可选的,也不能用[In, Out]替代。如果本机代码需要指针,则必须生成一个指针,这需要使用ref。然后,下一个考虑的是描述数据流的属性。 - Hans Passant
1
您可以在 learn.microsoft.com 上找到这些信息。 - Kelly Elton
显示剩余2条评论

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