【问题标题】:C# - Binary reader in Big Endian?C# - Big Endian 中的二进制阅读器?
【发布时间】:2012-01-27 02:22:01
【问题描述】:

我正在尝试通过使用程序读取所有不同的信息位来提高我对 STFS 文件格式的理解。使用一个参考哪些偏移量包含哪些信息的网站,我编写了一些代码,它有一个二进制阅读器通过文件并将值放入正确的变量中。

问题是所有数据都应该是大端,而二进制阅读器读取的所有内容都是小端。那么,解决此问题的最佳方法是什么?

我可以创建一个二进制读取器的模拟类,它返回一个反转的字节数组吗?我可以在类实例中更改一些内容,使其以大端方式读取,因此我不必重写所有内容?

感谢任何帮助。

编辑:我尝试添加 Encoding.BigEndianUnicode 作为参数,但它仍然读取 little endian。

【问题讨论】:

  • @HansPassant,这是需要我将代码开源的 dll 之一吗?为什么有些 dll 需要这样做?
  • Walkerneo 我删除了我的答案,因为 zmbq 在我之前 3 分钟回答了基本相同的问题。字节序的概念不适用于字节数组,仅适用于 word、dwords、qwords 等,即 2、4、8 等字节组。如果这意味着要更改大量代码,我很抱歉,但是男人必须做男人必须做的事情。
  • Skeet 卖书,代码几乎没有附加条件。检查该页面上的许可证部分。 Apache 术语在这里:apache.org/licenses/LICENSE-2.0.html
  • 如果您关心的是提取单词、dwords、qwords 等并一步将它们转换为正确的字节序,那么这个问题已经在别处得到了回答:stackoverflow.com/questions/1674160/…
  • @MikeNakis,哦,是的,您对字节数组是正确的。我还在学习:D

标签: c# endianness binaryreader


【解决方案1】:

我已经扩展了 Ian Kemp's 出色的建议,我正在使用新的 BinaryPrimitives,在 .NET Core 2.1+ 中可用,根据 Stephen Toub's post,它们的性能更高,并且可以在内部处理字节顺序和反转。

因此,如果您运行的是 .NET Core 2.1+,则绝对应该使用此版本:

public class EndiannessAwareBinaryReader : BinaryReader
{
    public enum Endianness
    {
        Little,
        Big,
    }

    private readonly Endianness _endianness = Endianness.Little;

    public EndiannessAwareBinaryReader(Stream input) : base(input)
    {
    }

    public EndiannessAwareBinaryReader(Stream input, Encoding encoding) : base(input, encoding)
    {
    }

    public EndiannessAwareBinaryReader(Stream input, Encoding encoding, bool leaveOpen) : base(
        input, encoding, leaveOpen)
    {
    }

    public EndiannessAwareBinaryReader(Stream input, Endianness endianness) : base(input)
    {
        _endianness = endianness;
    }

    public EndiannessAwareBinaryReader(Stream input, Encoding encoding, Endianness endianness) :
        base(input, encoding)
    {
        _endianness = endianness;
    }

    public EndiannessAwareBinaryReader(Stream input, Encoding encoding, bool leaveOpen,
        Endianness endianness) : base(input, encoding, leaveOpen)
    {
        _endianness = endianness;
    }

    public override short ReadInt16() => ReadInt16(_endianness);

    public override int ReadInt32() => ReadInt32(_endianness);

    public override long ReadInt64() => ReadInt64(_endianness);

    public override ushort ReadUInt16() => ReadUInt16(_endianness);

    public override uint ReadUInt32() => ReadUInt32(_endianness);

    public override ulong ReadUInt64() => ReadUInt64(_endianness);

    public short ReadInt16(Endianness endianness) => endianness == Endianness.Little
        ? BinaryPrimitives.ReadInt16LittleEndian(ReadBytes(sizeof(short)))
        : BinaryPrimitives.ReadInt16BigEndian(ReadBytes(sizeof(short)));

    public int ReadInt32(Endianness endianness) => endianness == Endianness.Little
        ? BinaryPrimitives.ReadInt32LittleEndian(ReadBytes(sizeof(int)))
        : BinaryPrimitives.ReadInt32BigEndian(ReadBytes(sizeof(int)));

    public long ReadInt64(Endianness endianness) => endianness == Endianness.Little
        ? BinaryPrimitives.ReadInt64LittleEndian(ReadBytes(sizeof(long)))
        : BinaryPrimitives.ReadInt64BigEndian(ReadBytes(sizeof(long)));

    public ushort ReadUInt16(Endianness endianness) => endianness == Endianness.Little
        ? BinaryPrimitives.ReadUInt16LittleEndian(ReadBytes(sizeof(ushort)))
        : BinaryPrimitives.ReadUInt16BigEndian(ReadBytes(sizeof(ushort)));

    public uint ReadUInt32(Endianness endianness) => endianness == Endianness.Little
        ? BinaryPrimitives.ReadUInt32LittleEndian(ReadBytes(sizeof(uint)))
        : BinaryPrimitives.ReadUInt32BigEndian(ReadBytes(sizeof(uint)));

    public ulong ReadUInt64(Endianness endianness) => endianness == Endianness.Little
        ? BinaryPrimitives.ReadUInt64LittleEndian(ReadBytes(sizeof(ulong)))
        : BinaryPrimitives.ReadUInt64BigEndian(ReadBytes(sizeof(ulong)));
}

【讨论】:

    【解决方案2】:

    与大多数这些答案不同,BinaryReader 的大部分完整(出于我的目的)替代品可以正确处理字节序。默认情况下,它的工作方式与BinaryReader 完全相同,但可以构造为以所需的字节顺序读取。此外,Read<Primitive> 方法被重载以允许您指定读取特定值的字节顺序 - 在您处理混合 LE/BE 数据流的(不太可能的)场景中很有用。

    public class EndiannessAwareBinaryReader : BinaryReader
    {
        public enum Endianness
        {
            Little,
            Big,
        }
    
        private readonly Endianness _endianness = Endianness.Little;
    
        public EndiannessAwareBinaryReader(Stream input) : base(input)
        {
        }
    
        public EndiannessAwareBinaryReader(Stream input, Encoding encoding) : base(input, encoding)
        {
        }
    
        public EndiannessAwareBinaryReader(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen)
        {
        }
    
        public EndiannessAwareBinaryReader(Stream input, Endianness endianness) : base(input)
        {
            _endianness = endianness;
        }
    
        public EndiannessAwareBinaryReader(Stream input, Encoding encoding, Endianness endianness) : base(input, encoding)
        {
            _endianness = endianness;
        }
    
        public EndiannessAwareBinaryReader(Stream input, Encoding encoding, bool leaveOpen, Endianness endianness) : base(input, encoding, leaveOpen)
        {
            _endianness = endianness;
        }
    
        public override short ReadInt16() => ReadInt16(_endianness);
    
        public override int ReadInt32() => ReadInt32(_endianness);
    
        public override long ReadInt64() => ReadInt64(_endianness);
    
        public override ushort ReadUInt16() => ReadUInt16(_endianness);
    
        public override uint ReadUInt32() => ReadUInt32(_endianness);
    
        public override ulong ReadUInt64() => ReadUInt64(_endianness);
    
        public short ReadInt16(Endianness endianness) => BitConverter.ToInt16(ReadForEndianness(sizeof(short), endianness));
    
        public int ReadInt32(Endianness endianness) => BitConverter.ToInt32(ReadForEndianness(sizeof(int), endianness));
    
        public long ReadInt64(Endianness endianness) => BitConverter.ToInt64(ReadForEndianness(sizeof(long), endianness));
    
        public ushort ReadUInt16(Endianness endianness) => BitConverter.ToUInt16(ReadForEndianness(sizeof(ushort), endianness));
    
        public uint ReadUInt32(Endianness endianness) => BitConverter.ToUInt32(ReadForEndianness(sizeof(uint), endianness));
    
        public ulong ReadUInt64(Endianness endianness) => BitConverter.ToUInt64(ReadForEndianness(sizeof(ulong), endianness));
    
        private byte[] ReadForEndianness(int bytesToRead, Endianness endianness)
        {
            var bytesRead = ReadBytes(bytesToRead);
    
            if ((endianness == Endianness.Little && !BitConverter.IsLittleEndian)
                || (endianness == Endianness.Big && BitConverter.IsLittleEndian))
            {
                Array.Reverse(bytesRead);
            }
    
            return bytesRead;
        }
    }
    

    【讨论】:

    • 最佳解决方案,处理主机系统字节序以及源数据字节序,并且仅在必须反转数据时才反转数据。
    • 很棒的解决方案,但是 BitConverter 方法需要附加一个额外的 startIndex 参数: public short ReadInt16(Endianness endianness) => BitConverter.ToInt16(ReadForEndianness(sizeof(short), endianness), 0);
    • @PeterWilson 你想在 .NET Framework 中使用它吗?
    • 这显然有效,但不是那么有效,因为 CPU 确实有用于字节顺序转换的单一指令,而不是数组操作。 bswap
    • 很好的解决方案,如果您使用.NET Core 2.1+,您可以使用this 使用BinaryPrimitives 的版本,因此反转由框架处理,根据斯蒂芬图布devblogs.microsoft.com/dotnet/….
    【解决方案3】:

    我通常不会回答自己的问题,但我已经通过一些简单的代码完成了我想要的:

    class BinaryReader2 : BinaryReader { 
        public BinaryReader2(System.IO.Stream stream)  : base(stream) { }
    
        public override int ReadInt32()
        {
            var data = base.ReadBytes(4);
            Array.Reverse(data);
            return BitConverter.ToInt32(data, 0);
        }
    
        public Int16 ReadInt16()
        {
            var data = base.ReadBytes(2);
            Array.Reverse(data);
            return BitConverter.ToInt16(data, 0);
        }
    
        public Int64 ReadInt64()
        {
            var data = base.ReadBytes(8);
            Array.Reverse(data);
            return BitConverter.ToInt64(data, 0);
        }
    
        public UInt32 ReadUInt32()
        {
            var data = base.ReadBytes(4);
            Array.Reverse(data);
            return BitConverter.ToUInt32(data, 0);
        }
    
    }
    

    我知道这就是我想要的,但我不知道怎么写。我找到了这个页面,它有帮助:http://www.codekeep.net/snippets/870c4ab3-419b-4dd2-a950-6d45beaf1295.aspx

    【讨论】:

    • 题外话,但你的班级字段(a16 等)是不必要的。您在构造过程中为它们分配一个数组,但在每个方法中,您将该数组替换为Read 函数返回的新数组。您可以将var a32 = base.ReadBytes... 放在每个方法中并去掉字段。
    • 它们不是不必要的,它们是有害的。将线程安全代码(可能忽略共享底层流)转换为共享状态情况。
    • 您可能想在反转之前检查BitConverter.IsLittleEndian。如果是false,则不需要反转。
    • @Gusdor 是的。您必须知道源数据的字节序和 BitConverter 的字节序,如果它们不匹配:反转它。
    • 为每次读取分配一个数组会为 GC 创建工作。您可以根据需要多次调用GetByte,然后将字节移位/或运算到位。
    【解决方案4】:

    在我看来,你要小心这样做。想要从 BigEndian 转换为 LittleEndian 的原因是,如果正在读取的字节在 BigEndian 中,并且针对它们计算的操作系统在 LittleEndian 中运行。

    C# 不再是一种仅适用于窗口的语言。使用 Mono 等端口,以及其他 Microsoft 平台,如 Windows Phone 7/8、Xbox 360/Xbox One、Windwos CE、Windows 8 Mobile、Linux With MONO、Apple with MONO 等。操作平台很可能在BigEndian,在这种情况下,如果你在不做任何检查的情况下转换代码,你会搞砸自己的。

    BitConverter 上面已经有一个名为“IsLittleEndian”的字段,您可以使用它来确定操作环境是否为 LittleEndian。然后你可以有条件地做反转。

    因此,我实际上只是写了一些 byte[] 扩展而不是创建一个大类:

        /// <summary>
        /// Get's a byte array from a point in a source byte array and reverses the bytes. Note, if the current platform is not in LittleEndian the input array is assumed to be BigEndian and the bytes are not returned in reverse order
        /// </summary>
        /// <param name="byteArray">The source array to get reversed bytes for</param>
        /// <param name="startIndex">The index in the source array at which to begin the reverse</param>
        /// <param name="count">The number of bytes to reverse</param>
        /// <returns>A new array containing the reversed bytes, or a sub set of the array not reversed.</returns>
        public static byte[] ReverseForBigEndian(this byte[] byteArray, int startIndex, int count)
        {
            if (BitConverter.IsLittleEndian)
                return byteArray.Reverse(startIndex, count);
            else
                return byteArray.SubArray(startIndex, count);
    
        }
    
        public static byte[] Reverse(this byte[] byteArray, int startIndex, int count)
        {
            byte[] ret = new byte[count];
            for (int i = startIndex + (count - 1); i >= startIndex; --i)
            {
                byte b = byteArray[i];
                ret[(startIndex + (count - 1)) - i] = b;
            }
            return ret;
        }
    
        public static byte[] SubArray(this byte[] byteArray, int startIndex, int count)
        {
            byte[] ret = new byte[count];
            for (int i = 0; i < count; ++i)            
                ret[0] = byteArray[i + startIndex];
            return ret;
        }
    

    想象一下这个示例代码:

    byte[] fontBytes = byte[240000]; //some data loaded in here, E.G. a TTF TrueTypeCollection font file. (which is in BigEndian)
    
    int _ttcVersionMajor = BitConverter.ToUint16(fontBytes.ReverseForBigEndian(4, 2), 0);
    
    //output
    _ttcVersionMajor = 1 //TCCHeader is version 1
    

    【讨论】:

      【解决方案5】:

      恕我直言,这是一个更好的答案,因为它不需要更新不同的类,使大端调用显而易见,并允许在流中混合大端和小端调用。

      public static class Helpers
      {
        // Note this MODIFIES THE GIVEN ARRAY then returns a reference to the modified array.
        public static byte[] Reverse(this byte[] b)
        {
          Array.Reverse(b);
          return b;
        }
      
        public static UInt16 ReadUInt16BE(this BinaryReader binRdr)
        {
          return BitConverter.ToUInt16(binRdr.ReadBytesRequired(sizeof(UInt16)).Reverse(), 0);
        }
      
        public static Int16 ReadInt16BE(this BinaryReader binRdr)
        {
          return BitConverter.ToInt16(binRdr.ReadBytesRequired(sizeof(Int16)).Reverse(), 0);
        }
      
        public static UInt32 ReadUInt32BE(this BinaryReader binRdr)
        {
          return BitConverter.ToUInt32(binRdr.ReadBytesRequired(sizeof(UInt32)).Reverse(), 0);
        }
      
        public static Int32 ReadInt32BE(this BinaryReader binRdr)
        {
          return BitConverter.ToInt32(binRdr.ReadBytesRequired(sizeof(Int32)).Reverse(), 0);
        }
      
        public static byte[] ReadBytesRequired(this BinaryReader binRdr, int byteCount)
        {
          var result = binRdr.ReadBytes(byteCount);
      
          if (result.Length != byteCount)
            throw new EndOfStreamException(string.Format("{0} bytes required from stream, but only {1} returned.", byteCount, result.Length));
      
          return result;
        }
      }
      

      【讨论】:

      • 回车前记得检查BitConverter.IsLittleEndian
      • 看起来你在 Reverse 之后需要 ".ToArray()",因为 Reverse 返回 IEnumerable 而不是 byte[] (这是 BitConverter 所期望的)
      • 从 .NET Core 开始,还有一个 BinaryPrimitives 类使其过时:docs.microsoft.com/en-us/dotnet/api/…
      【解决方案6】:

      我不熟悉 STFS,但更改字节顺序相对容易。 “网络顺序”是大端序,因此您只需将网络顺序转换为主机顺序即可。

      这很容易,因为已经有代码可以做到这一点。看IPAddress.NetworkToHostOrder,这里解释:ntohs() and ntohl() equivalent?

      【讨论】:

        猜你喜欢
        • 2015-03-14
        • 1970-01-01
        • 2019-06-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多