所以,这个问题有很多变种,看了好几个后我还是想不通。
这是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#中尝试过的最后一次尝试,看起来非常符合逻辑,但我收到错误“Method的签名与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);
答案 0 :(得分:21)
您选择的PInvoke签名有两个问题。
第一个很容易解决。你有unsigned long
误译。在C中,unsigned long
通常只有4个字节。您选择了8个字节的C#类型long
。更改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
}
这是一个没有成员的结构,对于编组人员而言,它的大小为128字节(作为奖励,它可以闪现!)。现在,我们可以轻松地将Frame
结构定义为uint
和此类型
struct Frame
{
public int Identifier;
public Blob NameBlob;
...
}
现在我们有一个blittable类型,其大小为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);
}
}
}
注意:我无法对调用约定部分发表评论,因为我不熟悉GetFrame
API,但我肯定会检查这些内容。
答案 1 :(得分:9)
问题是native函数返回一个非blittable类型作为返回值。
http://msdn.microsoft.com/en-us/library/ef4c3t39.aspx
P / Invoke不能将非blittable类型作为返回值。
您不能p / Invoke该方法。 [编辑 实际上可以,请参阅JaredPar's answer ]
按值返回132个字节是一个坏主意。如果这个本机代码是你的,我会修复它。您可以通过分配132个字节并返回指针来修复它。然后添加一个FreeFrame方法来释放该内存。现在它可以是p / Invoked。
或者,您可以将其更改为接受指向它将填充的Frame内存的指针。
答案 2 :(得分:3)
JaredPar的另一个选择是利用C#固定大小缓冲区功能。但是,这确实需要您打开设置以允许不安全的代码,但它避免了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));
}
}