web-dev-qa-db-ja.com

C#-ビッグエンディアンのバイナリリーダー?

プログラムを使用してさまざまな情報をすべて読み取ることで、STFSファイル形式の理解を深めようとしています。どのオフセットにどの情報が含まれているのかを参照するWebサイトを使用して、バイナリリーダーにファイルを通過させ、値を正しい変数に配置させるコードを書きました。

問題は、すべてのデータがビッグエンディアンであることがサポートされており、バイナリリーダーが読み取るものはすべてリトルエンディアンであるということです。それで、これを修正するための最善の方法は何ですか?

バイトの逆配列を返すBinaryリーダーの模倣クラスを作成できますか?すべてを書き換える必要がないように、ビッグエンディアンで読み取れるようにクラスインスタンスで変更できるものはありますか?

どんな助けでもありがたいです。

編集:Encoding.BigEndianUnicodeをパラメーターとして追加しようとしましたが、それでもリトルエンディアンが読み取られます。

30
mowwwalker

私は通常、自分の質問に答えるつもりはありませんが、いくつかの簡単なコードで、私が望んでいたことを正確に達成しました。

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

36
mowwwalker

IMHOは、別のクラスを更新する必要がないため、少し優れた答えであり、ビッグエンディアンの呼び出しを明白にし、ビッグエンディアンとリトルエンディアンの呼び出しをストリームに混在させることができます。

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;
  }
}
11
Tim Williams

私はSTFSに詳しくありませんが、エンディアンの変更は比較的簡単です。 「ネットワークオーダー」はビッグエンディアンなので、ネットワークからホストオーダーに変換するだけで済みます。

これを行うコードがすでにあるので、これは簡単です。見る IPAddress.NetworkToHostOrder、ここで説明します: ntohs()とntohl()は同等ですか?

7
zmbq

私の意見では、あなたはこれを行うことに注意したいのです。 BigEndianからLittleEndianに変換する理由は、読み込まれるバイトがBigEndianであり、それらに対して計算するOSがLittleEndianで動作している場合です。

C#は、ウィンドウのみの言語ではなくなりました。 Monoなどのポート、およびWindows Phone 7/8、Xbox 360/Xbox One、Windwos CE、Windows 8 Mobile、MONOを使用するLinuxなどの他のMicrosoftプラットフォームを使用する場合、Apple 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
Ryan Mann

真実のソース(ソースコード)を確認する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#L17

https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/io/memorystream.cs#L25

私が間違っていたり、何かを逃した場合は、誰かがチャイムしてください。

0
jjxtra