3

我在非托管 C/C++ 代码 (dll) 中有一个函数,它返回一个包含 char 数组的结构。我创建了 C# 结构来在调用函数时接收这个返回值。在调用这个函数时,我得到'System.Runtime.InteropServices.MarshalDirectiveException'

这是 C 声明:

typedef struct T_SAMPLE_STRUCT {
int num;
char text[20];
} SAMPLE_STRUCT;

SAMPLE_STRUCT sampleFunction( SAMPLE_STRUCT ss );

这是 C# 声明:

struct SAMPLE_STRUCT
{
    public int num;
    public string text;
}

class Dllwrapper
{
    [DllImport("samplecdll.dll")]
    public static extern SAMPLE_STRUCT sampleFunction(SAMPLE_STRUCT ss);

}

我正在使用 1 字节的 ASCII。

有没有人有关于如何做到这一点的提示或解决方案?

4

6 回答 6

5

转换 C 数组成员的技巧是使用 MarshalAs(UnmanagedType.ByValTStr)。这可用于告诉 CLR 将数组编组为内联成员与普通的非内联数组。试试下面的签名。

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet=System.Runtime.InteropServices.CharSet.Ansi)]
public struct T_SAMPLE_STRUCT {

    /// int
    public int num;

    /// char[20]
    [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst=20)]
    public string text;
}

public partial class NativeMethods {

    /// Return Type: SAMPLE_STRUCT->T_SAMPLE_STRUCT
    ///ss: SAMPLE_STRUCT->T_SAMPLE_STRUCT
    [System.Runtime.InteropServices.DllImportAttribute("<Unknown>", EntryPoint="sampleFunction")]
public static extern  T_SAMPLE_STRUCT sampleFunction(T_SAMPLE_STRUCT ss) ;

}

此签名由 CodePlex 上提供的 PInovke 互操作助手(链接)提供给您。它可以自动将大多数 PInvoke 签名从本机代码转换为 C# 或 VB.Net。

于 2009-04-27T12:19:04.380 回答
2

C中的结构定义:

#pragma pack(push, 1)
typedef struct T_SAMPLE_STRUCT {
  int num;
  char text[20];
};
#pragma pack(pop)

C# 中的定义:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct T_SAMPLE_STRUCT
{
  [MarshalAs(UnmanagedType.I4)]
  public int num;

  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
  public string text;
}
于 2009-04-30T13:08:04.813 回答
2

对于 P/Invoke 来编组来说,这不是一个简单的结构:编组包含 char* 而不是 char[] 的结构更容易(尽管您会遇到从非托管代码分配 char* 并随后将其释放的问题托管代码)。

假设您坚持当前的设计,一种选择是将字符串数组声明为:

public fixed char text[20];

不幸的是,您必须将unsafe关键字添加到访问此数组的任何代码中。

于 2009-04-27T12:00:56.373 回答
0

我设法通过将功能分开来做到这一点:

void receiveStruct( SAMPLE_STRUCT ss )
void returnStruct(SAMPLE_STRUCT &ss)

正如 JaredPar 告诉我的那样,我已经更改了结构定义:

[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct T_SAMPLE_STRUCT
{
    /// int
    public int num;

    /// char[20]
    [MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 20)]
    public string text;
}

现在它可以工作了。

谢谢!

于 2009-04-27T14:07:54.647 回答
0

有趣的是,既然你已经有了答案,这里的基本问题是结构只需要正确的大小。由于托管类型没有内联数组,因此您只需要弥补它原本需要的空间。当您使用 C++/CLI 编写代码时,您经常会看到带有显式 Size 参数的 StructLayoutAttribute。这使得运行时为该类型分配正确数量的内存,这使得它可以在本机端使用 blittable。因此,这些也应该起作用:

[StructLayout(LayoutKind.Sequential, Size=24)]
public struct T_SAMPLE_STRUCT
{    
    public int num;
    // to get the string here, you'd need to get a pointer
    public char firstChar;     
}

// or

[StructLayout(LayoutKind.Sequential)]
public struct T_SAMPLE_STRUCT
{    
    public int num;
    public byte c0;
    public byte c1;
    public byte c2;
    public byte c3;
    public byte c4;
    public byte c5;
    public byte c6;
    public byte c7;
    public byte c8;
    public byte c9;
    public byte c10;
    public byte c11;
    public byte c12;
    public byte c13;
    public byte c14;
    public byte c15;
    public byte c16;
    public byte c17;
    public byte c18;
    public byte c19;
}

当然,这些在托管代码中使用起来要困难得多(您需要复制内存或使用指针),但它们说明了 blittable 类型的概念,这主要是在本机代码和托管代码之间传递类型的方式。

于 2009-04-27T16:13:51.257 回答
-1

首先你必须把 StructLayout[Sequential] 属性放在你的结构上,我认为它会起作用

[ StructLayout( LayoutKind.Sequential, CharSet=CharSet.Ansi )]
struct SAMPLE_STRUCT
{    
    public int num;
    [ MarshalAs( UnmanagedType.ByValArray, SizeConst=20 )] 
    public char[] text;
} 
于 2009-04-27T12:17:40.047 回答