将 C Headers 转换为 C# - 结构内固定字符数组的 ByValArray 与 ByValTStr
Converting C Headers to C# - ByValArray versus ByValTStr for Fixed Char Array inside a Structure
我在 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 生成的,并且是我 google 搜索中多个帖子推荐的 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"
当我在调试时检查 object 时,我可以在该字段中看到这 4 个字符。但是,当我使用此结构调用 MQTTClient_connect() 时,"MQTC" 被截断为 "MQT".
当我单步执行代码时,只要我单步执行 进入 MQTTClient_connect,struct_id 字段就会从 "MQTC" 变为 "MQT[ =55=]" 在 C# object 检查器中,MQTTClient_connect 失败,因为 struct_id 不是预期的。
如果我改为在 C# 中这样定义结构:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] struct_id;
并像这样设置它的值:
struct_id = Encoding.ASCII.GetBytes("MQTC");
然后一切正常???
我的目标是了解封送处理和 P/Invoke 并将 C/C++ headers 转换为 C# 代码,所以我真的很想知道:
1 - 为什么在使用 "string" 时使用 "byte[]" 会导致 struct_id 的值在我 进入 时发生变化 MQTTClient_connect()套路?
2 - 有没有一种方法可以使用 "string" 定义 C# 结构,这将使我的其余 C# 代码更简单?
谢谢!
原因是 p/invoke 编组器必须决定 char[4]
字段是文本还是二进制(即字节)。此处的惯例是将编组为 UnmanagedType.ByValTStr
的 string
假定为文本。在这种情况下,它可以具有可变长度,这由必须存在的空终止符决定。这是与 char
的固定长度数组的典型用法相匹配的约定,用于保存 C 字符串。 C 字符串以 null 结尾。
但实际上,我怀疑您的数据不是真正的文本。我怀疑该字段最好在 C 中声明为 unsigned char struct_id[4]
以指示它包含 4 个字节。不过,所有这些都有些主观,当然,我们并不认同图书馆设计背后的所有基本原理。也许有一些很好的理由将它声明为我看不到的 char
数组。
无论如何,您的 C# 代码都不能对此字段使用 string
。 byte[]
和 SizeConst = 4
是编组的正确方法。记录类型上的一些辅助方法可以帮助在字节数组和字符串之间进行转换。但是,我想知道您会遇到多少不同的 ID。可能你只需要声明一些字节数组常量,你可以使用它们而不是字符串文字。
我在 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 生成的,并且是我 google 搜索中多个帖子推荐的 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"
当我在调试时检查 object 时,我可以在该字段中看到这 4 个字符。但是,当我使用此结构调用 MQTTClient_connect() 时,"MQTC" 被截断为 "MQT".
当我单步执行代码时,只要我单步执行 进入 MQTTClient_connect,struct_id 字段就会从 "MQTC" 变为 "MQT[ =55=]" 在 C# object 检查器中,MQTTClient_connect 失败,因为 struct_id 不是预期的。
如果我改为在 C# 中这样定义结构:
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] struct_id;
并像这样设置它的值:
struct_id = Encoding.ASCII.GetBytes("MQTC");
然后一切正常???
我的目标是了解封送处理和 P/Invoke 并将 C/C++ headers 转换为 C# 代码,所以我真的很想知道:
1 - 为什么在使用 "string" 时使用 "byte[]" 会导致 struct_id 的值在我 进入 时发生变化 MQTTClient_connect()套路?
2 - 有没有一种方法可以使用 "string" 定义 C# 结构,这将使我的其余 C# 代码更简单?
谢谢!
原因是 p/invoke 编组器必须决定 char[4]
字段是文本还是二进制(即字节)。此处的惯例是将编组为 UnmanagedType.ByValTStr
的 string
假定为文本。在这种情况下,它可以具有可变长度,这由必须存在的空终止符决定。这是与 char
的固定长度数组的典型用法相匹配的约定,用于保存 C 字符串。 C 字符串以 null 结尾。
但实际上,我怀疑您的数据不是真正的文本。我怀疑该字段最好在 C 中声明为 unsigned char struct_id[4]
以指示它包含 4 个字节。不过,所有这些都有些主观,当然,我们并不认同图书馆设计背后的所有基本原理。也许有一些很好的理由将它声明为我看不到的 char
数组。
无论如何,您的 C# 代码都不能对此字段使用 string
。 byte[]
和 SizeConst = 4
是编组的正确方法。记录类型上的一些辅助方法可以帮助在字节数组和字符串之间进行转换。但是,我想知道您会遇到多少不同的 ID。可能你只需要声明一些字节数组常量,你可以使用它们而不是字符串文字。