C# - Big Endian 中的二进制阅读器?

发布于 2024-12-22 11:48:47 字数 330 浏览 2 评论 0原文

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

问题是所有数据都应该是 Big Endian,而二进制读取器读取的所有数据都是 Little Endian。那么,解决这个问题的最佳方法是什么?

我可以创建一个返回反向字节数组的二进制读取器的模拟类吗?我可以在类实例中更改一些内容,使其以大尾数法读取,这样我就不必重写所有内容吗?

任何帮助表示赞赏。

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

I'm trying to improve my understanding of the STFS file format by using a program to read all the different bits of information. Using a website with a reference of which offsets contain what information, I wrote some code that has a binary reader go through the file and place the values in the correct variables.

The problem is that all the data is SUPPOSED to be Big Endian, and everything the binary reader read is Little Endian. So, what's the best way to go about fixing this?

Can I create a mimic class of Binary reader that returns a reversed array of bytes? Is there something I can change in class instance that will make it read in big endian so I don't have to rewrite everything?

Any help is appreciated.

edit: I tried adding Encoding.BigEndianUnicode as a parameter, but it still reads little endian.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

揽月 2024-12-29 11:48:47

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

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

I'm not usually one to answer my own questions, but I've accomplished exactly what I wanted with some simple code:

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

}

I knew that's what I wanted, but I didn't know how to write it. I found this page and it helped: http://www.codekeep.net/snippets/870c4ab3-419b-4dd2-a950-6d45beaf1295.aspx

夜空下最亮的亮点 2024-12-29 11:48:47

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

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

IMHO a slightly better answer as it doesn't require a different class to be newed-up, makes the big-endian calls obvious and allows big- and little-endian calls to be mixed in the stream.

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;
  }
}
情深如许 2024-12-29 11:48:47

与大多数答案不同,BinaryReader 的基本完整(出于我的目的)替代品可以正确处理字节顺序。默认情况下,它的工作方式与 BinaryReader 完全相同,但可以构造为以所需的字节顺序读取。此外,Read 方法被重载,允许您指定读取特定值的字节序 - 在您处理混合 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;
    }
}

A mostly-complete (for my purposes) drop-in replacement for BinaryReader that handles endianness correctly, unlike most of these answers. By default it works exactly like BinaryReader, but can be constructed to read in the required endianness. Additionally the Read<Primitive> methods are overloaded to allow you to specify the endianness to read a particular value in - useful in the (unlikely) scenario that you're dealing with a stream of mixed LE/BE data.

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;
    }
}
对风讲故事 2024-12-29 11:48:47

我不熟悉 STFS,但更改字节序相对容易。 “网络顺序”是大端顺序,因此您需要做的就是从网络顺序转换为主机顺序。

这很容易,因为已经有代码可以做到这一点。查看 IPAddress.NetworkToHostOrder,如下所述:ntohs() 和 ntohl()等价?

I'm not familiar with STFS, but changing endianess is relatively easy. "Network Order" is big endian, so all you need to do is translate from network to host order.

This is easy because there's already code that does that. Look at IPAddress.NetworkToHostOrder, as explained here: ntohs() and ntohl() equivalent?

泪眸﹌ 2024-12-29 11:48:47

我认为,这样做要小心。人们想要从 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

In my opinion, you want to be careful doing this. The reason one would want to Convert from BigEndian to LittleEndian is if the bytes being read are in BigEndian and the OS calculating against them is operating in LittleEndian.

C# isn't a window only language anymore. With ports like Mono, and also other Microsoft Platforms like Windows Phone 7/8, Xbox 360/Xbox One, Windwos CE, Windows 8 Mobile, Linux With MONO, Apple with MONO, etc. It is quite possible the operating platform could be in BigEndian, in which case you'd be screwing yourself if you converted the code without doing any checks.

The BitConverter already has a field on it called "IsLittleEndian" you can use this to determine if the operating environment is in LittleEndian or not. Then you can do the reversing conditionally.

As such, I actually just wrote some byte[] extensions instead of making a big class:

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

So imagine this example code:

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
倾城花音 2024-12-29 11:48:47

您最好使用 BinaryPrimitives< /a> 类

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

You better to use BinaryPrimitives class

        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));
        }
长发绾君心 2024-12-29 11:48:47

我扩展了 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)));
}

I've expanded on Ian Kemp's excellent suggestion, I'm using the new BinaryPrimitives, available in .NET Core 2.1+, they are more performant according to Stephen Toub's post and can handle the endianness and reversal internally.

So if you are running .NET Core 2.1+ you should definitely use this version:

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)));
}
烟柳画桥 2024-12-29 11:48:47

您可能会喜欢这个选项,它受到其他一些答案的启发,但一般性地编写,以便实现更简洁的实现,使我们能够包含所有变体,而无需太多额外的代码。

public static class BinaryReaderEndianExtensions
{
    public static T ReadNativeEndian<T>(this BinaryReader reader, Func<byte[], T> converter) =>
        converter(reader.ReadBytesRequired(Marshal.SizeOf<T>()));
    public static T ReadForeignEndian<T>(this BinaryReader reader, Func<byte[], T> converter) =>
        converter(reader.ReadBytesRequired(Marshal.SizeOf<T>()).Reverse().ToArray());

    public static T ReadBigEndian<T>(this BinaryReader reader, Func<byte[], T> converter) =>
        BitConverter.IsLittleEndian ? reader.ReadForeignEndian(converter) : reader.ReadNativeEndian(converter);
    public static T ReadLittleEndian<T>(this BinaryReader reader, Func<byte[], T> converter) =>
        BitConverter.IsLittleEndian ? reader.ReadNativeEndian(converter) : reader.ReadForeignEndian(converter);

    public static UInt16 ReadUInt16BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToUInt16(bytes));
    public static Int16 ReadInt16BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToInt16(bytes));
    public static UInt32 ReadUInt32BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToUInt32(bytes));
    public static Int32 ReadInt32BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToInt32(bytes));
    public static UInt64 ReadUInt64BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToUInt64(bytes));
    public static Int64 ReadInt64BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToInt64(bytes));
    public static Double ReadDoubleBE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToDouble(bytes));
    public static Single ReadSingleBE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToSingle(bytes));
    public static Half ReadHalfBE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToHalf(bytes));

    public static UInt16 ReadUInt16LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToUInt16(bytes));
    public static Int16 ReadInt16LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToInt16(bytes));
    public static UInt32 ReadUInt32LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToUInt32(bytes));
    public static Int32 ReadInt32LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToInt32(bytes));
    public static UInt64 ReadUInt64LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToUInt64(bytes));
    public static Int64 ReadInt64LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToInt64(bytes));
    public static Double ReadDoubleLE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToDouble(bytes));
    public static Single ReadSingleLE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToSingle(bytes));
    public static Half ReadHalfLE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToHalf(bytes));

    public static byte[] ReadBytesRequired(this BinaryReader reader, int count)
    {
        byte[] bytes = reader.ReadBytes(count);
        return bytes.Length == count ? bytes
            : throw new EndOfStreamException(string.Format("{0} bytes required from stream, but only {1} returned.", count, bytes.Length));
    }
}

You might like this option, inspired by some of the other answers but written generically so as to make for a more concise implementation that allows us to include all the variations without too much extra code.

public static class BinaryReaderEndianExtensions
{
    public static T ReadNativeEndian<T>(this BinaryReader reader, Func<byte[], T> converter) =>
        converter(reader.ReadBytesRequired(Marshal.SizeOf<T>()));
    public static T ReadForeignEndian<T>(this BinaryReader reader, Func<byte[], T> converter) =>
        converter(reader.ReadBytesRequired(Marshal.SizeOf<T>()).Reverse().ToArray());

    public static T ReadBigEndian<T>(this BinaryReader reader, Func<byte[], T> converter) =>
        BitConverter.IsLittleEndian ? reader.ReadForeignEndian(converter) : reader.ReadNativeEndian(converter);
    public static T ReadLittleEndian<T>(this BinaryReader reader, Func<byte[], T> converter) =>
        BitConverter.IsLittleEndian ? reader.ReadNativeEndian(converter) : reader.ReadForeignEndian(converter);

    public static UInt16 ReadUInt16BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToUInt16(bytes));
    public static Int16 ReadInt16BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToInt16(bytes));
    public static UInt32 ReadUInt32BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToUInt32(bytes));
    public static Int32 ReadInt32BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToInt32(bytes));
    public static UInt64 ReadUInt64BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToUInt64(bytes));
    public static Int64 ReadInt64BE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToInt64(bytes));
    public static Double ReadDoubleBE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToDouble(bytes));
    public static Single ReadSingleBE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToSingle(bytes));
    public static Half ReadHalfBE(this BinaryReader reader) => reader.ReadBigEndian(bytes => BitConverter.ToHalf(bytes));

    public static UInt16 ReadUInt16LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToUInt16(bytes));
    public static Int16 ReadInt16LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToInt16(bytes));
    public static UInt32 ReadUInt32LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToUInt32(bytes));
    public static Int32 ReadInt32LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToInt32(bytes));
    public static UInt64 ReadUInt64LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToUInt64(bytes));
    public static Int64 ReadInt64LE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToInt64(bytes));
    public static Double ReadDoubleLE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToDouble(bytes));
    public static Single ReadSingleLE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToSingle(bytes));
    public static Half ReadHalfLE(this BinaryReader reader) => reader.ReadLittleEndian(bytes => BitConverter.ToHalf(bytes));

    public static byte[] ReadBytesRequired(this BinaryReader reader, int count)
    {
        byte[] bytes = reader.ReadBytes(count);
        return bytes.Length == count ? bytes
            : throw new EndOfStreamException(string.Format("{0} bytes required from stream, but only {1} returned.", count, bytes.Length));
    }
}
北风几吹夏 2024-12-29 11:48:47

我已将 Big Endian 读取方法实现为 BinaryReader 上的扩展方法。

我发现使用 BinaryReader 的派生类没有真正的优势,因为实际有用的内部方法(例如 ReadOnlySpanInternalRead())被标记为 私有而不是内部。因此,扩展方法。

我尝试尽可能接近 BinaryReader 的 LE 方法的内部行为。这需要实现 BinaryReader.InternalRead() 的替代方案。我已将 Microsoft 的 MemoryStream 优化包含为 .TryReadSpanUnsafe(),尽管我不确定当缓冲区如此小且调用开销如此之大时它有多大用处设置直接缓冲区访问的公共方法。

public static class BinaryReaderBigEndianExtensions
{
    public static short ReadInt16BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadInt16BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(2, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[2]));

    public static ushort ReadUInt16BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadUInt16BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(2, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[2]));

    public static int ReadInt32BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadInt32BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(4, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[4]));

    public static uint ReadUInt32BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadUInt32BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(4, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[4]));

    public static long ReadInt64BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadInt64BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(8, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[8]));

    public static ulong ReadUInt64BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadUInt64BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(8, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[8]));

    public static Half ReadHalfBigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadHalfBigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(2, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[2]));

    public static float ReadSingleBigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadSingleBigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(4, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[4]));

    public static double ReadDoubleBigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadDoubleBigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(8, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[8]));

    private static ReadOnlySpan<byte> ReadSpan(this BinaryReader binaryReader, Span<byte> buffer)
    {
        binaryReader.BaseStream.ReadExactly(buffer);
        return buffer;
    }

    private static bool TryReadSpanUnsafe(this MemoryStream memoryStream, int numBytes, out ReadOnlySpan<byte> readBytes)
    {
        if (memoryStream.TryGetBuffer(out var msBuffer))
        {
            readBytes = msBuffer.AsSpan((int)memoryStream.Position, numBytes);
            memoryStream.Seek(numBytes, SeekOrigin.Current);
            return true;
        }
        else
        {
            readBytes = [];
            return false;
        }
    }
}

如果您不需要任何浮点方法,则可以简单地使用 BinaryPrimitives.ReverseEndianness(),因为 BinaryReader 方法保证返回小尾数法。在小端系统上,这基本上将具有最佳情况的性能,因为它是 BinaryPrimitives.ReadInt16BigEndian() 内部执行的操作。在大端系统上,字节序将反转两次并增加一些开销,但绝大多数运行 .NET 的系统都不是大端。

public static class BinaryReaderBigEndianExtensions
{
    public static short ReadInt16BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadInt16());
    public static ushort ReadUInt16BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadUInt16());
    public static int ReadInt32BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadInt32());
    public static uint ReadUInt32BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadUInt32());
    public static long ReadInt64BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadInt64());
    public static ulong ReadUInt64BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadUInt64());
}

I've implemented Big Endian read methods as extension methods on BinaryReader.

I found that there's no real advantage to using a derived class of BinaryReader due to the actually useful internal methods (like ReadOnlySpan<byte> InternalRead()) being marked private instead of internal. Hence, extension methods.

I have attempted to remain as close to the internal behaviour of BinaryReader's LE methods as possible. This requires implementing an alternative to BinaryReader.InternalRead(). I've included Microsoft's MemoryStream optimization as .TryReadSpanUnsafe(), although I'm not sure it has much use when the buffers are so small and there's so much overhead calling into the public methods to set up the direct buffer access.

public static class BinaryReaderBigEndianExtensions
{
    public static short ReadInt16BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadInt16BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(2, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[2]));

    public static ushort ReadUInt16BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadUInt16BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(2, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[2]));

    public static int ReadInt32BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadInt32BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(4, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[4]));

    public static uint ReadUInt32BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadUInt32BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(4, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[4]));

    public static long ReadInt64BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadInt64BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(8, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[8]));

    public static ulong ReadUInt64BigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadUInt64BigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(8, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[8]));

    public static Half ReadHalfBigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadHalfBigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(2, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[2]));

    public static float ReadSingleBigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadSingleBigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(4, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[4]));

    public static double ReadDoubleBigEndian(this BinaryReader binaryReader) => BinaryPrimitives.ReadDoubleBigEndian(
        binaryReader.BaseStream is MemoryStream ms && ms.TryReadSpanUnsafe(8, out ReadOnlySpan<byte> readBytes) ? readBytes : binaryReader.ReadSpan(stackalloc byte[8]));

    private static ReadOnlySpan<byte> ReadSpan(this BinaryReader binaryReader, Span<byte> buffer)
    {
        binaryReader.BaseStream.ReadExactly(buffer);
        return buffer;
    }

    private static bool TryReadSpanUnsafe(this MemoryStream memoryStream, int numBytes, out ReadOnlySpan<byte> readBytes)
    {
        if (memoryStream.TryGetBuffer(out var msBuffer))
        {
            readBytes = msBuffer.AsSpan((int)memoryStream.Position, numBytes);
            memoryStream.Seek(numBytes, SeekOrigin.Current);
            return true;
        }
        else
        {
            readBytes = [];
            return false;
        }
    }
}

If you don't need any of the floating point methods, you can simply use BinaryPrimitives.ReverseEndianness(), since the BinaryReader methods are guaranteed to return little endian. On a little endian system, this will have basically best-case performance, since it's what BinaryPrimitives.ReadInt16BigEndian() does internally anyway. On a big endian system, the endianness will be reversed twice and add some overhead, but the vast majority of systems running .NET are not big endian.

public static class BinaryReaderBigEndianExtensions
{
    public static short ReadInt16BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadInt16());
    public static ushort ReadUInt16BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadUInt16());
    public static int ReadInt32BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadInt32());
    public static uint ReadUInt32BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadUInt32());
    public static long ReadInt64BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadInt64());
    public static ulong ReadUInt64BigEndian(this BinaryReader br) => BinaryPrimitives.ReverseEndianness(br.ReadUInt64());
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文