Маршал структура C, содержащая массив переменной длины

Я хотел бы маршалировать структуру C с массивом переменной длины обратно в C#, но до сих пор я не могу получить ничего лучше, чем представление указателя на структуру и указатель на float.

неуправляемый представление:

typedef float        smpl_t;

typedef struct {
  uint_t length;  /**< length of buffer */
  smpl_t *data;   /**< data vector of length ::fvec_t.length */
} fvec_t;

управлял представление:

[StructLayout(LayoutKind.Sequential)]
public unsafe struct fvec_t1
{
    public uint length;

    public float* data;
}

[DllImport("libaubio-4.dll", EntryPoint = "new_fvec", PreserveSig = true, CharSet = CharSet.Ansi,
    CallingConvention = CallingConvention.Cdecl)]
public static extern unsafe fvec_t1* new_fvec1(uint length);

Я хотел бы иметь массив стилей .NET, где data будет float[] но если я изменю структуру на форму ниже, я получу не могу взять адрес, получить размер, или объявить указатель на управляемый тип во внешней функции выше.

[StructLayout(LayoutKind.Sequential)]
public unsafe struct fvec_t1
{
    public uint length;

    public float[] data;
}

видимо, невозможно иметь массив переменной длины, маршалированный назад как-есть, это правильно или все еще есть способ достичь этого ?

2 ответов


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

но если вы знаете размер, он будет выглядеть следующим образом:

int arr[15]

вы сможете организовать его так:

[MarshalAs(UnmanagedType.LPArray, SizeConst=15)] int[] arr

если вы не знаете длину массива, и это то, что вы хотите вы можете преобразовать его в intprt и иметь дело с inptr, но сначала вам нужно создать 2 структуры

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct fvec_t1
{
    public uint whatever;

    public int[] data;
}

другой, как показано ниже:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
struct fvec_t2{
    public uint whatever;
}

создайте функцию для инициализации массива, как показано ниже

private static int[] ReturnIntArray()
{
    int [] myInt = new int[30];

    for (int i = 0; i < myInt.length; i++)
    {
        myInt[i] = i + 1;
    }

    return myInt;
}

создать первую структуру

fvec_t1 instance = new fvec_t1();
instance.whatever=10;
instance.data= ReturnIntArray();

создать второй структуры

fvec_t2 instance1 = new fvec_t2();

instance1.whatever = instance.whatever

динамически выделять пространство для структуры fvec_t2 с расширенным пространством для массива данных

IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(fvec_t2)) + Instance.data.Length);

перенесите существующие значения полей fvec_t2 в пространство памяти, на которое указывает ptr

Marshal.StructureToPtr(instance1, ptr, true);

вычислить смещение поля массива данных, которое должно быть в конце fvec_t2 struct

int offset = Marshal.SizeOf(typeof(fvec_t2));

получить адрес памяти поля массива данных на основе смещения.

IntPtr address = new IntPtr(ptr.ToInt32() + offset);

копировать данные в ptr

Marshal.Copy(instance.data, 0, address, instance.data.Length);

сделать вызов

bool success = dllfunction(ptr);

Marshal.FreeHGlobal(ptr);
ptr = IntPtr.Zero;

в приведенном выше случае я бы определенно использовал SizeParamIndex.

[StructLayout(LayoutKind.Sequential)]
public struct fvec_t1
{
    uint length;
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] float[] data;
}

удачи.