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

kev*_*key 18 c# pinvoke

所以,这个问题已经出现了很多变种,在看了几个后我仍然无法弄明白.

这是C代码:

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

Frame GetFrame(int index);
Run Code Online (Sandbox Code Playgroud)

这是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);
Run Code Online (Sandbox Code Playgroud)

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

谢谢,凯文

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

我应该改变我的C代码:

void GetFrame(int index, Frame * f);
Run Code Online (Sandbox Code Playgroud)

并使用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);
Run Code Online (Sandbox Code Playgroud)

Jar*_*Par 21

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

第一个很容易解决.你有误译unsigned long.在C中,a unsigned long通常只有4个字节.您选择了long8个字节的C#类型.更改要使用的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
}
Run Code Online (Sandbox Code Playgroud)

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

struct Frame
{
    public int Identifier;
    public Blob NameBlob;
    ...
}
Run Code Online (Sandbox Code Playgroud)

现在我们有一个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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @BenVoigt我不认为用户在任何一种情况下都会遇到编译器错误.我相信这是一个运行时错误.C#编译器在其任何消息中都不使用工作PInvoke (2认同)

Ter*_*ver 10

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

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

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

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

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

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