Маршалинг структур C в C#
Предположим, у меня есть структура:
typedef struct {
float x;
float y;
float z;
int ID;
} Vertex;
и функция C++:
float first(Vertex* ptr, int length){ //really silly function, just an example
Vertex u,v;
u.x = ptr[0].x; //...and so on, copy x,y,z,ID
v.x = ptr[1].x;
return (u.x * v.x + u.y * v.y + u.z * v.z);
}
Vertex* another(float a, int desired_size){
Vertex v = (Vertex*)malloc(desired_size*sizeof(Vertex));
v[0].x = a;
v[1].x = -a; //..and so on.. make some Vertices.
return v;
}
сначала-моя IDE. Я использую Visual Studio 2010, создавая приложение C# (4.0); часть c++ также встроена в VS2010.
Я знаю, как построить DLL кода C / C++ и использовать его в приложении C#, но до сегодняшнего дня я использовал только примитивные Аргументы и возвращаемые значения, такие как:
[DllImport("library.dll", CharSet = CharSet.Ansi, SetLastError = true, CallingConvention = CallingConvention.StdCall)]
public static extern int simple(int a, int b);
сегодня мне нужно сдать массив of структуры (as в приведенном выше примере).. и, возможно, также получить один обратно..
как "перевести" класс C# в структуру C (и наоборот) ??
2 ответов
структура может быть объявлена следующим образом:
[StructLayout(LayoutKind.Sequential)]
public struct Vertex {
public float x;
public float y;
public float z;
public int ID;
}
Далее вам нужно остановиться на конвенции вызова. Ваш код c++ почти наверняка скомпилирован с cdecl
. Давайте придерживаться этого.
сначала функцию легко вызвать из C#:
[DllImport("library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern float first(Vertex[] vertices);
обратите внимание, что вы не должны использовать SetLastError
здесь-это для функций Windows API. И нет необходимости устанавливать CharSet
так как здесь нет текста.
теперь another
все становится сложнее. Если вы можете выделить память в коде C#, то это определенно путь.
void PopulateVertices(Vertex *vertices, int count)
{
for (int i=0; i<count; i++)
{
vertices[i].x = ....
}
}
на стороне c# вы объявляете это так:
[DllImport("library.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void PopulateVertices(Vertex[] vertices, int count);
и назовем это так
Vertex[] vertices = new Vertex[2];
PopulateVertices(vertices, vertices.Length);
если вы не хотите выделять на стороне C# забора, то сделайте это так:
- верните указатель из кода C++ и выделите его с помощью
CoTaskMemAlloc
. - в C# объявите возвращаемое значение импортированная функция как
IntPtr
. - использовать
Marshal.PtrToStructure
и некоторая арифметика указателя для маршалирования возвращаемого массива в массив C#. - вызов
Marshal.FreeCoTaskMem
чтобы освободить память, выделенную в родной модуль.
но если вам нужен мой совет, попробуйте выделить массив в управляемом коде.
это должно быть так просто:
[StructLayout(LayoutKind.Sequential)]
public struct Vertex {
float x;
float y;
float z;
int ID;
}
[DllImport("library.dll", CallingConvention=CallingConvention.StdCall)]
public static extern float first(Vertex[] verticies, int arrLen);
проблемы мая run into будет, если есть какая-либо упаковка, выполненная на версии C структуры, и, возможно, макет структуры. Если макет не соответствует, вы можете изменить его в LayoutKind.Explicit
и с помощью [FieldOffset(0)]
атрибут для каждого поля. C также не имеет понятия, длина массива verticies передана, поэтому, если это изменится,вы захотите передать это методу.
чтобы получить массив назад:
[DllImport("library.dll", CallingConvention=CallingConvention.StdCall)]
public static extern Vertex[] another(float a);
упаковщик обрабатывает все проблемы с памятью при передаче аргументов, но вернув время он ничего не может сделать. Поскольку память выделяется в неуправляемой куче, GC понятия не имеет об этом, и вы будет в конечном итоге с утечкой памяти. Маршаллер просто скопирует собственные структуры в управляемый массив struct, но не сможет освободить память, выделенную с помощью malloc
.
самый простой способ обойти это, если вы можете изменить код c++, было бы изменить подпись another
чтобы взять массив вершин (и длину массива) вместо того, чтобы возвращать его. Мне не нужно писать какой-либо код для вас, который делает это, @DavidHeffernan уже сделал это в своем ответе, часть перерыва.