C# P/Invoke | Несогласованное поведение сортировки массива blittable структуры?
Сегодня я тестировал материал с 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;
}
}
А также программа на C#, получающая доступ к этим функциям через P/Invoke:
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
Из этого вопроса:"Происходит, когда значение является blittable, дорогое слово, которое означает, что управляемое значение или макет объекта идентичен нативному макету. Затем маршаллер pinvoke может воспользоваться ярлыком, закрепив объект и передав указатель на управляемый объект хранилище. Вы неизбежно увидите изменения, поскольку нативный код напрямую изменяет управляемый объект."
Насколько я понимаю, S должен быть blittable (так как он использует последовательную разметку и содержит только поля типов, которые являются blittable), и поэтому должен быть закреплен маршаллером, вызывая модификации, которые "переносятся", как они делают с PassIntArray. Где разница и почему?