数组的 C# 互操作封送处理行为似乎与文档不一致

C# Interop Marshaling behaviour for arrays seems inconsistent with documentation

我目前正在为 OpenGL 编写精简的 C# 绑定。我最近刚刚实现了 OpenGL GenVertexArrays 函数,它具有以下签名: OpenGL Documentation on glGenVertexArrays.

本质上,您向它传递一个数组,其中存储由 OpenGL 创建的顶点数组生成的对象值。

为了创建绑定,我使用委托,因为 glGenVertexArrays 是一个 OpenGL 扩展函数,所以我必须使用 wglGetProcAddress 动态加载它。我在 C# 中定义的委托签名如下所示:

[SuppressUnmanagedCodeSecurity]
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate void glGenVertexArrays(uint amount, uint[] array);

使用 Marshal.GetDelegateForFunctionPointer 检索函数指针并将其转换为该委托,如下所示:

IntPtr proc = wglGetProcAddress(name);

del = Marshal.GetDelegateForFunctionPointer(proc, delegateType);

无论如何,这就是困扰我的地方:

在我能找到的关于引用类型(包括数组)的默认编组行为的任何官方文档中,是这样的:

By default, reference types (classes, arrays, strings, and interfaces) passed by value are marshaled as In parameters for performance reasons. You do not see changes to these types unless you apply InAttribute and OutAttribute (or just OutAttribute) to the method parameter.

这取自这个 MSDN 页面:MSDN page on directional attributes

但是,从我的委托签名可以看出,[In] 和 [Out] 方向属性还没有用在无符号整数数组上,这意味着当我调用这个函数时,我实际上应该无法查看 OpenGL 应该存储在其中的生成的对象值。除了,我是。使用此签名,当 运行 调试器时,我可以得到以下结果:

可以看出,调用确实影响了数组,即使我没有明确使用 [Out] 属性。据我了解,这不是我应该期望的结果。

有人知道这背后的原因吗?我知道这似乎是一件小事,但我很好奇为什么这似乎会破坏 Microsoft 描述的默认编组行为。与纯平台调用原型相比,调用委托时是否有一些幕后发生的事情?还是我误解了文档?

[编辑]

对于任何好奇的人,调用委托的 public 方法是在静态 "GL" class 上定义的,如下所示:

public static void GenVertexArrays(uint amount, uint[] array)
{
    InvokeExtensionFunction<glGenVertexArrays>()(amount, array);
}

您链接的文档页面上没有提到它,但是 another topic 专门用于数组的编组,它说:

With pinning optimization, a blittable array can appear to operate as an In/Out parameter when interacting with objects in the same apartment.

这两个条件在您的案例中都得到了满足:uint 数组是 blittable 的,并且没有机器到机器的封送处理。将其声明为 [Out] 仍然是一个好主意,因此您的意图已记录在代码中。

文档在一般情况下是正确的。但是uint有点特殊,是blittable类型。一个昂贵的词,意味着 pinvoke 编组器不需要做任何特殊的事情来转换数组元素值。 C# 中的 uint 与 C 中的 unsigned int 完全相同。这不是巧合,它是处理器可以本机处理的类型。

因此编组器可以简单地固定数组并将指向第一个数组元素的指针作为第二个参数传递。非常快,总是你想要的。并且该函数直接涂写到托管数组中,因此不需要将值复制回来。也有点危险,你永远不想在 amount 论点上撒谎,GC 堆损坏是一个很难诊断的错误。

大多数简单值类型和简单值类型的结构都是 blittable。 bool 是一个明显的例外。否则,即使没有必要,您也永远不必为使用 [Out] 感到抱歉。编组器在这里简单地忽略它。