Подтвердить что ты не робот

Функция С#, вызывающая C, которая возвращает структуру с фиксированным размером char array

Итак, было много вариантов этого вопроса, и, посмотрев на несколько, я все еще не могу понять.

Это код C:

typedef struct
{
unsigned long Identifier;
char Name[128];
} Frame;

Frame GetFrame(int index);

Это код С#:

struct Frame
{
    public ulong Identifier;
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 128)]
    public char[] Name;
}

[DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern Frame GetFrame(int index);

Это последняя попытка, которую я пробовал на С#, и она кажется довольно логичной, но я получаю сообщение об ошибке "Подпись метода не совместима с PInvoke". Итак, я как бы потерял то, что попробовать дальше. Любая помощь приветствуется.

Спасибо, Кевин

Обновлено. Кевин добавил это как отредактировать мой ответ

Вместо этого я должен изменить свой код C:

void GetFrame(int index, Frame * f);

и вместо этого используйте С#:

struct Frame
{
    public uint Identifier;
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string Name;
}

[DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern void GetFrame(int index, ref Frame f);
4b9b3361

Ответ 1

Проблема заключается в том, что нативная функция возвращает не-blittable-тип в качестве возвращаемого значения.

http://msdn.microsoft.com/en-us/library/ef4c3t39.aspx

P/Invoke не может иметь не-blittable типы в качестве возвращаемого значения.

Вы не можете p/вызывать этот метод. [ РЕДАКТИРОВАТЬ На самом деле это возможно, см. ответ JaredPar]

Возвращение 132 байта по значению - плохая идея. Если этот родной код является вашим, я бы это исправить. Вы можете исправить это, выделив 132 байта и вернув указатель. Затем добавьте метод FreeFrame для освобождения этой памяти. Теперь это может быть p/Вызывается.

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

Ответ 2

Есть две проблемы с выбранной вами подписью PInvoke.

Первое легко исправить. У вас есть неверный перевод unsigned long. В C a unsigned long обычно всего 4 байта. Вы выбрали тип С# long, который составляет 8 байтов. Изменение кода С# для использования uint исправит это.

Второе немного сложнее. Как указал Тэргивер, CLR Marshaller поддерживает только структуру в возвратной позиции, если она будет светить. Blittable - это причудливый способ сказать, что он имеет то же самое представление памяти в собственном и управляемом коде. Выбранное определение структуры не является blittable, потому что оно имеет вложенный массив.

Это можно обойти, но если вы помните, что PInvoke - очень простой процесс. CLR Marshaller действительно просто вам нужно ответить на 2 вопроса с сигнатурой ваших типов и методами pinvoke.

  • Сколько байтов я копирую?
  • В каком направлении им нужно идти?

В этом случае количество байтов sizeof(unsigned long) + 128 == 132. Таким образом, все, что нам нужно сделать, это создать управляемый тип, который является blittable и имеет размер 132 байта. Самый простой способ сделать это - определить blob для обработки массива

[StructLayout(LayoutKind.Sequential, Size = 128)]
struct Blob
{
   // Intentionally left empty. It just a blob
}

Это структура без элементов, которые будут отображаться маршаллеру как имеющие размер 128 байт (и в качестве бонуса он будет смягчать!). Теперь мы можем легко определить структуру Frame как комбинацию uint, и этот тип

struct Frame
{
    public int Identifier;
    public Blob NameBlob;
    ...
}

Теперь у нас есть blittable type с размером, который marshaller увидит как 132 байта. Это означает, что он будет отлично работать с подписью GetFrame, которую вы определили

Остальная часть - это доступ к фактическому char[] для имени. Это немного сложно, но может быть решена с помощью мажоритарной магии.

public string GetName()
{
    IntPtr ptr = IntPtr.Zero;
    try
    {
        ptr = Marshal.AllocHGlobal(128);
        Marshal.StructureToPtr(NameBlob, ptr, false);
        return Marshal.PtrToStringAnsi(ptr, 128);
    }
    finally
    {
        if (ptr != IntPtr.Zero) 
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

Примечание. Я не могу прокомментировать часть соглашения о вызове, потому что я не знаком с API GetFrame, но что-то, что я обязательно проверю.

Ответ 3

Другой вариант JaredPar - использовать функцию буфера фиксированного размера С#. Тем не менее это требует от вас включить параметр, чтобы разрешить небезопасный код, но он избегает наличия двух структур.

class Program
{
    private const int SIZE = 128;

    unsafe public struct Frame
    {
        public uint Identifier;
        public fixed byte Name[SIZE];
    }

    [DllImport("PinvokeTest2.DLL", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    private static extern Frame GetFrame(int index);

    static unsafe string GetNameFromFrame(Frame frame)
    {
        //Option 1: Use if the string in the buffer is always null terminated
        //return Marshal.PtrToStringAnsi(new IntPtr(frame.Name));

        //Option 2: Use if the string might not be null terminated for any reason,
        //like if were 128 non-null characters, or the buffer has corrupt data.

        return Marshal.PtrToStringAnsi(new IntPtr(frame.Name), SIZE).Split('\0')[0];
    }

    static void Main()
    {
        Frame a = GetFrame(0);
        Console.WriteLine(GetNameFromFrame(a));
    }
}