将大端字节集合编组到结构中以提取值
有一个关于读取 C/C++ 数据的富有洞察力的问题来自字节数组的 C# 结构,但我无法让代码适用于我的大端(网络字节顺序)字节集合。 (编辑:请注意,我的真实结构不止一个字段。)有没有一种方法可以将字节编组到结构的大端版本中,然后以框架的字节顺序(主机的字节顺序)提取值,通常是小端)?
(请注意,反转字节数组将不起作用 - 每个值的字节都必须反转,这不会为您提供与反转所有字节相同的集合。)
这应该总结我正在寻找的内容for (LE=LittleEndian, BE=BigEndian):
void Main()
{
var leBytes = new byte[] {1, 0, 2, 0};
var beBytes = new byte[] {0, 1, 0, 2};
Foo fooLe = ByteArrayToStructure<Foo>(leBytes);
Foo fooBe = ByteArrayToStructureBigEndian<Foo>(beBytes);
Assert.AreEqual(fooLe, fooBe);
}
[StructLayout(LayoutKind.Explicit, Size=4)]
public struct Foo {
[FieldOffset(0)]
public ushort firstUshort;
[FieldOffset(2)]
public ushort secondUshort;
}
T ByteArrayToStructure<T>(byte[] bytes) where T: struct
{
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T));
handle.Free();
return stuff;
}
T ByteArrayToStructureBigEndian<T>(byte[] bytes) where T: struct
{
???
}
其他有用的链接:
There is an insightful question about reading a C/C++ data structure in C# from a byte array, but I cannot get the code to work for my collection of big-endian (network byte order) bytes. (EDIT: Note that my real struct has more than just one field.) Is there a way to marshal the bytes into a big-endian version of the structure and then pull out the values in the endianness of the framework (that of the host, which is usually little-endian)?
(Note, reversing the array of bytes will not work - each value's bytes must be reversed, which does not give you the same collection as reversing all of the bytes.)
This should summarize what I'm looking for (LE=LittleEndian, BE=BigEndian):
void Main()
{
var leBytes = new byte[] {1, 0, 2, 0};
var beBytes = new byte[] {0, 1, 0, 2};
Foo fooLe = ByteArrayToStructure<Foo>(leBytes);
Foo fooBe = ByteArrayToStructureBigEndian<Foo>(beBytes);
Assert.AreEqual(fooLe, fooBe);
}
[StructLayout(LayoutKind.Explicit, Size=4)]
public struct Foo {
[FieldOffset(0)]
public ushort firstUshort;
[FieldOffset(2)]
public ushort secondUshort;
}
T ByteArrayToStructure<T>(byte[] bytes) where T: struct
{
GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
T stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(),typeof(T));
handle.Free();
return stuff;
}
T ByteArrayToStructureBigEndian<T>(byte[] bytes) where T: struct
{
???
}
Other helpful links:
Byte of a struct and onto endian concerns
A little more on bytes and endianness (byte order)
Read binary files more efficiently using C#
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
这是交换字节序的另一种解决方案。
它是根据 Adam Robinsons 解决方案进行调整的:https://stackoverflow.com/a/2624377/1254743
它甚至能够处理嵌套结构。
Here's another solution for swapping endianness.
It's adjusted from Adam Robinsons solution here: https://stackoverflow.com/a/2624377/1254743
It's even capable of handling nested structs.
正如我在对 @weismat 的回答的评论中提到的,有一种简单的方法可以实现大端结构。它涉及双重反转:原始字节完全反转,然后结构本身就是原始(大端)数据格式的反转。
Main
中的fooLe
和fooBe
的所有字段都具有相同的值。 (当然,通常情况下,小端结构和字节不会出现,但这清楚地显示了字节顺序之间的关系。)注意:请参阅 更新了代码,包括如何从结构中获取字节。
As alluded to in my comment on @weismat's answer, there is an easy way to achieve big-endian structuring. It involves a double-reversal: the original bytes are reversed entirely, then the struct itself is the reversal of the original (big-endian) data format.
The
fooLe
andfooBe
inMain
will have the same values for all fields. (Normally, the little-endian struct and bytes wouldn't be present, of course, but this clearly shows the relationship between the byte orders.)NOTE: See updated code including how to get bytes back out of the struct.
似乎必须有一个更优雅的解决方案,但这至少应该让你继续下去:
It seems there must be a more elegant solution, but this should at least get you going:
我终于找到了一种不涉及反射并且主要是用户友好的方法。它使用 Mono 的 DataConverter 类 (源),不幸的是,目前存在相当多的错误。 (例如,浮点数和双精度数似乎无法正常工作,字符串解析被破坏等)。
技巧是将字节解包并重新打包为大端字节序,这需要一个字符串来描述字节中的类型数组(请参阅最后一个方法)。此外,字节对齐很棘手:结构中有四个字节而不是一个,因为封送处理似乎依赖于 4 字节对齐(我仍然不太明白那部分)。 (编辑:我发现将
Pack=1
添加到StructLayout
属性 通常会处理字节对齐问题。)请注意,此示例代码在 LINQPad 中使用 - Dump 扩展方法仅打印信息关于该对象并返回该对象(它是流畅的)。
I finally figured out a way that didn't involve reflection and is mostly user-friendly. It uses Mono's DataConverter class (source) which, unfortunately, is fairly buggy at this point. (For example, floats and doubles don't seem to work correctly, string parsing is broken, etc.)
The trick is to Unpack and re-pack the bytes as big-endian, which requires a string describing what types are in the byte array (see the last method).Also, byte alignment is tricky: there are four bytes in the struct instead of one because marshaling seems to rely on having 4-byte alignment (I still don't quite understand that part). (EDIT: I have found that adding
Pack=1
to theStructLayout
attribute usually takes care of byte-alignment issues.)Note, this sample code was used in LINQPad - the Dump extension method just prints info about the object and returns the object (it's fluent).
我同意@weismat 的观点,并相信没有解决方案。
您在示例中显示的是,您可以像访问任何其他结构一样访问原始字节缓冲区,而无需对其进行任何更改,不复制或移动数据,什么也不做。只需将其固定以避免它因 GC 而移动。
这基本上就是您通常在 C 中通过使用包含目标结构和相同大小的字节数组的联合类型来实现的。
好的一面是它确实非常高效。
这有几个缺点,主要的一个缺点是您只能通过这种方式访问本机机器顺序(无论是 LE 还是 BE)的数据。因此,您的 ByteArrayToStructure 并不是真正的 LE,只是因为下面的处理器是 LE。如果您在另一个恰好是 BE 的目标上编译相同的程序,它会以另一种方式工作并相信您的字节数组是 BE。
其他缺点是您必须对数据对齐非常谨慎,注意可能的填充等,当然,如果不移动字节数组中的数据,就无法将字节顺序从 LE 更改为 BE(如果您有 16 位)仅整数数组,如您的示例中所示,这只是每两个字节交换一次)。
我碰巧遇到了类似的问题,并且由于之前的缺点而考虑不使用此解决方案,并选择将我的输入结构隐藏在访问器后面以隐藏对下面的字节数组的访问。它可能不那么优雅,但它很简单,并且还避免以任何方式复制缓冲区或移动数据。
I agree with @weismat and believe there is no solution.
What you show in your example is that you can access a raw byte buffer as if it were any OTHER structure without changing anything to it, not copying or moving data around, nothing. Just pinning it to avoid it to move around because of GC.
This is basically what you usually achieve in C by using a union type containing both your target structure and a byte array of the same size.
The good side is that it is really efficient.
That has several drawbacks, the main one being that you can only get access this way to data that are in the native machine order (be it LE or BE). Hence your ByteArrayToStructure is not really LE, it is only so because the processor underneath is LE. If you compile the same program on another target that happen to be BE, it works the other way and believe your byte array is BE.
Other drawbacks are that you must be very cautious with data alignment, be aware of possible padding, etc. and of course that there is no way to change byte order from LE to BE without moving data in bytes array (if you have a 16 bits integers only array as in your example this is merely swapping every two bytes).
I happened to have a similar problem and poundered not to use this solution because of the previous drawbacks and opted to hide my input structures behind accessors to hide access to the bytes array underneath. It may not be as elegant, but it is simple and also avoid to copy the buffer or move data in any way.
您尝试过 MiscUtil 吗?它有一个名为
EndianBitConverter
的实用程序类,用于在大端和小端字节数组之间进行转换。Have you tried MiscUtil? It has a utility class named
EndianBitConverter
to convert between big and little endian byte arrays.从我的角度来看,您只需要在字节数组转换之前添加 Array.Reverse() 即可。
From my point of view you just need to add an Array.Reverse() before the conversion of the byte array.
传统的解决方案是使用 ntohl() 和 ntohs()。
上面的代码适用于任何具有 BSD 套接字的平台,无论它是大端、小端还是 VAX 等完全奇怪的平台。相反的操作是使用 hton*() 完成的。
在大端平台上,这些函数通常是无操作的,因此应该是零成本。
The traditional solution is to use ntohl() and ntohs().
The above works on any platform that has BSD Sockets, no matter whether it's big-endian, little-endian, or something utterly weird like a VAX. The reverse operation is done using hton*().
On big-endian platforms the functions are usually no-ops and should therefore be zero cost.