Маршалинг структур 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# забора, то сделайте это так:

  1. верните указатель из кода C++ и выделите его с помощью CoTaskMemAlloc.
  2. в C# объявите возвращаемое значение импортированная функция как IntPtr.
  3. использовать Marshal.PtrToStructure и некоторая арифметика указателя для маршалирования возвращаемого массива в массив C#.
  4. вызов 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 уже сделал это в своем ответе, часть перерыва.