将C标头转换为C# - ByValArray与ByValTStr转换为结构内的固定字符数组

问题描述 投票:1回答:1

我有一个在C中定义的结构:

typedef struct {
    char struct_id[4];
    int struct_version;
    int keepAliveInterval;
    ……
} MQTTClient_connectOptions

我在C#中创建了一个相应的结构,如下所示:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MQTTClient_connectOptions {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string struct_id;
    public int struct_version;
    public int keepAliveInterval;
    ……
}

这个C#结构是由P / Invoke Interop Assistant生成的,是我谷歌搜索中多个帖子推荐的C#片段。

定义C结构的相同DLL /源代码也定义了函数:

DLLExport  int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options);

我在C#代码中定义为:

[DllImport("PahoMqttC", EntryPoint = "MQTTClient_connect", CharSet = CharSet.Ansi)]
public static extern int MQTTClient_connect(IntPtr handle, ref MQTTClient_connectOptions options);

在我的C#代码中,我可以设置

MQTTClient_connectOptions.struct_id = "MQTC"

当我在调试时检查对象时,我可以在该字段中看到这4个字符。但是,当我使用此结构调用MQTTClient_connect()时,“MQTC”将被截断为“MQT”。

当我单步执行代码时,只要进入MQTTClient_connect,struct_id字段就会在C#对象检查器中从“MQTC”更改为“MQT \ 0”,并且MQTTClient_connect失败,因为struct_id不是预期的结果。

如果我改为在C#中定义结构,如下所示:

 [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] struct_id;

并设置其值如下:

struct_id = Encoding.ASCII.GetBytes("MQTC");

然后一切正常???

我的目标是了解Marshaling和P / Invoke以及将C / C ++标头转换为C#代码,所以我真的想知道:

1 - 为什么在使用“string”时使用“byte []”会导致在进入MQTTClient_connect()例程时struct_id的值发生变化?

2 - 有没有办法使用“string”定义C#结构,这将使我的C#代码的其余部分更简单?

谢谢!

c# c pinvoke marshalling
1个回答
2
投票

其原因是p / invoke marshaller必须决定char[4]字段是文本还是二进制(即字节)。这里的惯例是,string被编组为UnmanagedType.ByValTStr被认为是文本。在这种情况下,它可以具有可变长度,该长度由必须存在的空终止符确定。这是与char的固定长度数组的典型使用相匹配的惯例,用于保存C字符串。 C字符串以空值终止。

但实际上,我怀疑你的数据并不是真正的文本。我怀疑该字段在C中更好地声明为unsigned char struct_id[4],表示它包含4个字节。所有这些都是有点主观的,当然我们并不是图书馆设计背后的所有理由的一方。也许有一些很好的理由将它声明为我看不到的char阵列。

无论如何,你的C#代码都不能在这个领域使用string。与byte[]SizeConst = 4是组织这个的正确方法。记录类型上的一些辅助方法可以帮助在字节数组和字符串之间进行转换。但是,我想知道您将遇到多少个不同的ID。可能你需要声明一些字节数组常量,你可以使用它们而不是字符串文字。

© www.soinside.com 2019 - 2024. All rights reserved.