我正在尝试将网络数据包发送到硬件设备。我想使用一个灵活的,面向对象的方法,所以我可以在高层次上使用它。该数据包有几个可变长度的字段。显然字节布局非常特殊。
这是一个表示我需要发送的数据包的结构:
[StructLayout(LayoutKind.Sequential)]
public struct Packet
{
public UInt16 Instruction;
public UInt16 Length; //length of data field
public UInt32 SessionHandle;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] SenderContext;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] MessageData;
}
MessageData[]
可以是任意长度,我将它固定为8字节作为启动器。
这是我尝试创建byte[]
以通过Socket发送它:
public static byte[] ToBytes(Packet ep)
{
Byte[] bytes = new Byte[Marshal.SizeOf(typeof(Packet))];
GCHandle pinStructure = GCHandle.Alloc(ep, GCHandleType.Pinned);
try
{
Marshal.Copy(pinStructure.AddrOfPinnedObject(), bytes, 0, bytes.Length);
return bytes;
}
finally
{
pinStructure.Free();
}
}
但我收到了:
ArgumentException : Object contains non-primitive or non-blittable data.
我认为在结构中设置SizeConst
可以解决这个问题吗?无论如何,我比这更迷失,因为硬件设备期待一个可变长度的数据包,我想利用它。
我可以手动将数据包逐字节放在一起,一切都很好。但我知道必须有更好的方法,而且我必须走错路。
有什么想法吗?
答案 0 :(得分:2)
CLR不允许您使用具有与引用类型重叠的字段的结构。在你的情况下两个数组。它与垃圾收集器完全不兼容,它无法跟踪对象引用。并且会非常不安全,因为它允许后门进入堆,直接操作对象引用的值。
在您的特定情况下这样做是没有意义的,因为两个阵列重叠并且大小完全相同。一个人可以完成工作。
答案 1 :(得分:0)
问题是数组不是blittable,也不是打包到struct中。它们毕竟是参考类型。所以struct实际上只包含对堆上的数组对象的引用。据我所知,MarshalAs
属性不会影响成员的布局。它只会影响成员在互操作例程中的编组方式。
我的另一个观察是你实际上是固定结构的盒装版本,而不是实际的结构。当然,值类型存在于堆栈中,因此无需固定它。也许你已经知道了,这就是你选择从中提取IntPtr
的方法。这没关系,但您可以使用unsafe
代码或Marshal.StructureToPtr
方法更高效,更合适地完成同样的事情。
我认为您最好的办法是创建一个单独的byte
数组并使用Array.Copy
方法和BitConverter
类。