11

假设有一个具有如下所示纯 C 接口的本机函数,从本机 DLL 导出:

// NativeDll.cpp

extern "C" void __stdcall FillArray(
    int fillValue, 
    int count, 
    int* data)
{
    // Assume parameters are OK...

    // Fill the array
    for (int i = 0; i < count; i++)
    {
        data[i] = fillValue;
    }
}

以下 P/Invoke 工作正常(使用 VS2010 SP1 测试):

[DllImport("NativeDll.dll", CallingConvention=CallingConvention.StdCall)]
public static extern void FillArray(
    int fillValue,
    int count,
    [In, Out] int[] data
);

以及这个 P/Invoke,与上面相同,但没有[In, Out]属性

[DllImport("NativeDll.dll", CallingConvention=CallingConvention.StdCall)]
public static extern void FillArray(
    int fillValue,
    int count,
    int[] data
);

那么,这些[In, Out]属性对于编组数组是可选的吗?如果有的话,他们的目的是什么?可以在我们的 P/Invoke 声明中省略它们吗?

4

1 回答 1

21

不,它们不是完全可选的。它只是偶然地起作用。然而,这是一个非常常见的事故。它之所以有效,是因为数组实际上并没有被封送。pinvoke marshaller 发现 C# 数组已经与本机数组兼容,因此跳过创建它的副本的步骤。它只是固定数组并将指针传递给本机代码。

这当然是非常高效的,并且您将不可避免地得到结果,因为本机代码直接写入数组元素。所以 [In] 和 [Out] 属性都不重要。

如果数组元素类型不是那么简单,它会变得更加模糊。识别不可 blittable 的结构或类类型的元素类型或编组后其布局不匹配的元素类型并不容易,因此 pinvoke 编组必须制作数组的副本。尤其是布局不兼容可能很难识别,因为托管布局是不可发现的。并且可以根据使用的抖动进行更改。例如,它可能在x86中工作,例如x64,选择Anycpu时令人讨厌。让它将修改后的副本复制回 C# 数组确实需要 [Out]。

不知道该建议什么,除了没有人因为在声明中明确而被解雇。当数组元素类型不简单时,也许你应该总是明确的,这样你就不会发生意外。

于 2013-01-16T19:39:43.990 回答