C#调用C函数返回具有固定大小char数组的struct

所以,这个问题有很多变种,看了好几个后我仍然无法弄明白。

这是C代码:

typedef struct { unsigned long Identifier; char Name[128]; } Frame; Frame GetFrame(int index); 

这是C#代码:

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

这是我在C#中尝试过的最后一次尝试,看起来非常符合逻辑,但我收到错误“方法的签名与PInvoke不兼容”。 所以,我有点迷失下一步的尝试。 任何帮助表示赞赏。

谢谢,凯文

Kevin 更新 了这个作为我的答案的编辑

我应该改为我的C代码:

 void GetFrame(int index, Frame * f); 

而是用于C#:

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

问题是本机函数返回非blittable类型作为返回值。

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

P / Invoke不能将非blittable类型作为返回值。

你不能p / Invoke该方法。 [ 编辑 它实际上是可能的,请参阅JaredPar的答案 ]

按值返回132个字节是个坏主意。 如果这个本机代码是你的,我会解决它。 您可以通过分配132个字节并返回指针来修复它。 然后添加一个FreeFrame方法来释放该内存。 现在它可以是p / Invoked。

或者,您可以将其更改为接受指向它将填充的Frame内存的指针。

您选择的PInvoke签名有两个问题。

第一个很容易修复。 你有一个unsigned long的误译。 在C中, unsigned long通常只有4个字节。 您选择了C#类型long ,即8个字节。 更改C#代码以使用uint将解决此问题。

第二个有点困难。 正如Tergiver指出的那样,CLR Marshaller只支持返回位置的结构,如果它是blittable的话。 Blittable是一种奇特的方式,它说它在本机代码和托管代码中具有完全相同的内存表示。 您选择的结构定义不是blittable,因为它有一个嵌套数组。

如果您记得PInvoke是一个非常简单的过程,这可以解决。 CLR Marshaller真的只需要你用你的类型和pinvoke方法的签名回答2个问题

  • 我复制了多少字节?
  • 他们需要去哪个方向?

在这种情况下,字节数是sizeof(unsigned long) + 128 == 132 。 所以我们需要做的就是构建一个blittable的托管类型,其大小为132字节。 最简单的方法是定义一个blob来处理数组部分

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

这是一个没有成员的结构,在marshaller中看起来大小为128字节(作为奖励它是blittable!)。 现在我们可以轻松地将Frame结构定义为uint和此类型的组合

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

现在我们有一个blittable类型,其大小将被编组为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); } } } 

注意:我无法对调用约定部分发表评论,因为我不熟悉GetFrame API,但我肯定会检查它。

JaredPar的另一个选择是利用C#固定大小缓冲区function。 但是,这确实需要您打开设置以允许不安全的代码,但它避免了2个结构。

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