我今天正在测试P / Invoke的东西,并遇到了让我很困惑的事情。
我有一个非托管库,其函数采用数组参数,打印出值并修改它们:
#include <cstdio>
#define export extern "C" void __cdecl __declspec(dllexport)
struct S
{
int x;
};
export PassIntArray(int* x, int size)
{
for (int i = 0; i < size; i++)
{
printf("x[%i] = %i\n", i, x[i]);
x[i] *= 10;
}
}
export PassSArray(S* s, int size)
{
for (int i = 0; i < size; i++)
{
printf("s[%i].x = %i\n", i, s[i].x);
s[i].x *= 10;
}
}
以及通过P / Invoke访问这些功能的C#程序:
using System.Runtime.InteropServices;
using static System.Console;
namespace Test
{
[StructLayout(LayoutKind.Sequential)]
struct S
{
public int X;
}
class Program
{
[DllImport("mylib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void PassIntArray(int[] x, int size);
[DllImport("mylib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void PassSArray(S[] s, int size);
static void Main(string[] args)
{
var z = new[] {1,2,3};
PassIntArray(z, z.Length);
foreach (var i in z)
WriteLine(i);
var u = new[] { new S { X = 1 }, new S { X = 2 }, new S { X = 3 } };
PassSArray(u, u.Length);
foreach (var i in u)
WriteLine(i.X);
}
}
}
运行该程序,数组函数的输出如下:
// Unmanaged side:
x[0] = 1
x[1] = 2
x[2] = 3
// Managed side, after modification:
10
20
30
但是,对于PassSArray,就是这样:
// Unmanaged side:
s[0].x = 1
s[1].x = 2
s[2].x = 3
// Managed side, after modification:
1
2
3
来自this question: “当值为blit时发生,一个昂贵的单词意味着托管值或对象布局与本机布局相同。然后,pinvoke marshaller可以获取快捷方式,固定对象并将指针传递给托管对象存储你将不可避免地看到变化,因为本机代码直接修改了托管对象。“
根据我的理解,S应该是blittable(因为它使用顺序布局并且只包含blittable类型的字段),因此应该由marshaller固定,导致修改'结转',就像使用PassIntArray一样。 区别在哪里,为什么?