Выделение структур C на C # - PullRequest
8 голосов
/ 30 ноября 2011

Предположим, у меня есть структура:

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);

Сегодня мне нужно передать массив из структур (как в примере выше) .. и, возможно, также получить один обратно ..

Как мне "перевести" класс C # в структуру C (и наоборот) ??

Ответы [ 2 ]

8 голосов
/ 30 ноября 2011

Структура может быть объявлена ​​следующим образом:

[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, чтобы освободить память, выделенную в собственном модуле.

Но если вы хотите мой совет, попробуйте выделить массив в управляемомкод.

3 голосов
/ 30 ноября 2011

Это должно быть так просто:

[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);

Проблемы, с которыми вы можете столкнуться, могут возникнуть, если будет выполнена какая-либо упаковка в 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 уже сделал это в своем ответе, часть перерыва.

...