Функция С#, вызывающая 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);
Ответы
Ответ 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));
}
}