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

时间:2011-12-23 21:43:45

标签: c# endianness binaryreader

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

问题是所有数据都被支持为Big Endian,二进制阅读器读取的所有内容都是Little Endian。那么,解决这个问题的最佳方法是什么?

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

感谢任何帮助。

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

7 个答案:

答案 0 :(得分:35)

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

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

答案 1 :(得分:11)

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

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

答案 2 :(得分:7)

我不熟悉STFS,但改变结束性相对容易。 “网络订单”是一个大端,所以你需要做的就是从网络转换到主机订单。

这很简单,因为已有代码可以做到这一点。请查看IPAddress.NetworkToHostOrder,如此处所述:ntohs() and ntohl() equivalent?

答案 3 :(得分:5)

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

C#不再是一种仅限窗口的语言。使用像Mono这样的端口,以及其他微软平台,如Windows Phone 7/8,Xbox 360 / Xbox One,Windwos CE,Windows 8移动版,带有MONO的Linux,带有MONO的Apple等等。操作平台很可能在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

答案 4 :(得分:1)

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

        switch (endianness)
        {
            case Endianness.Little:
                if (!BitConverter.IsLittleEndian)
                {
                    Array.Reverse(bytesRead);
                }
                break;

            case Endianness.Big:
                if (BitConverter.IsLittleEndian)
                {
                    Array.Reverse(bytesRead);
                }
                break;
        }

        return bytesRead;
    }
}

答案 5 :(得分: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)));
}

答案 6 :(得分:0)

检查真相的源(源代码)我看到BinaryWriter正在以大端序格式写入数据。在BinaryReader中,它采用大字节序格式,并将数据适当地读入int,无论CPU的体系结构如何,该数据都可以工作。您应该能够使用大端字节或小端字节进行写入,而无需其他代码即可在其他CPU架构上进行读取。可能是.NET核心中的新功能...

因此,如果您的源文件/流使用大尾数法,则应该能够使用BinaryReader读取数据,而无需额外的代码。

来源:

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/io/binarywriter.cs#L279

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/io/binaryreader.cs#L173

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/io/memorystream.cs#L253

如果我错了或错过了什么,请有人鸣叫。