c# -二进制读取器在大端序

本文关键字:二进制 读取 | 更新日期: 2023-09-27 18:18:53

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

问题是所有的数据都应该是大端序,而二进制读取器读取的所有数据都是小端序。那么,解决这个问题的最好方法是什么?

我可以创建一个模拟类的二进制阅读器,返回一个反向数组的字节?是否有一些我可以在类实例中改变,使它以大端读,所以我不必重写一切?

任何帮助都是感激的。

我试着添加编码。BigEndianUnicode作为参数,但它仍然读取小端序

c# -二进制读取器在大端序

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

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

我认为这是一个稍微好一点的答案,因为它不需要新创建一个不同的类,使大端调用变得明显,并允许大端和小端调用在流中混合。

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;
  }
}

我不熟悉STFS,但更改尾序相对容易。"Network Order"是大端序,所以你需要做的就是把网络顺序转换成主机顺序。

这很简单,因为已经有这样做的代码。看看IPAddress.NetworkToHostOrder,正如这里所解释的:ntohs()和ntohl()等效吗?

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;
    }
}

在我看来,这样做要小心。从BigEndian转换为LittleEndian的原因是,如果要读取的字节是BigEndian的,而操作系统对它们进行计算的操作是LittleEndian的。

c#不再仅仅是一种窗口语言了。移植Mono,以及其他微软平台,如Windows Phone 7/8, Xbox 360/Xbox One, Windows 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

您最好使用BinaryPrimitives类

        public override double ReadDouble()
        {
            return BinaryPrimitives.ReadDoubleBigEndian(ReadBytes(8));
        }
        public override short ReadInt16()
        {
            return BinaryPrimitives.ReadInt16BigEndian(ReadBytes(2));
        }
        public override int ReadInt32()
        {
            return BinaryPrimitives.ReadInt32BigEndian(ReadBytes(4));
        }
        public override long ReadInt64()
        {
            return BinaryPrimitives.ReadInt64BigEndian(ReadBytes(8));
        }
        public override float ReadSingle()
        {
            return BinaryPrimitives.ReadSingleBigEndian(ReadBytes(4));
        }
        public override ushort ReadUInt16()
        {
            return BinaryPrimitives.ReadUInt16BigEndian(ReadBytes(2));
        }
        public override uint ReadUInt32()
        {
            return BinaryPrimitives.ReadUInt32BigEndian(ReadBytes(4));
        }
        public override ulong ReadUInt64()
        {
            return BinaryPrimitives.ReadUInt64BigEndian(ReadBytes(8));
        }

我已经扩展了Ian Kemp的优秀建议,我正在使用新的BinaryPrimitives,可在。net Core 2.1+中使用,根据Stephen Toub的帖子,它们的性能更高,可以在内部处理端序和反转。

所以如果你正在运行。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)));
}