web-dev-qa-db-fra.com

C # - Lecteur binaire en Big Endian?

J'essaie d'améliorer ma compréhension du format de fichier STFS en utilisant un programme pour lire tous les différents bits d'information. En utilisant un site Web avec une référence dont les décalages contiennent quelles informations, j'ai écrit du code qui a un lecteur binaire parcourir le fichier et placer les valeurs dans les variables correctes.

Le problème est que toutes les données sont censées être Big Endian, et tout ce que le lecteur binaire lit est Little Endian. Alors, quelle est la meilleure façon de résoudre ce problème?

Puis-je créer une classe synoptique de lecteur binaire qui renvoie un tableau inversé d'octets? Y a-t-il quelque chose que je peux changer dans l'instance de classe qui le fera lire en big endian donc je n'ai pas à tout réécrire?

Toute aide est appréciée.

edit: J'ai essayé d'ajouter Encoding.BigEndianUnicode comme paramètre, mais il lit toujours peu d'endian.

30
mowwwalker

Je ne suis généralement pas du genre à répondre à mes propres questions, mais j'ai accompli exactement ce que je voulais avec un code simple:

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

}

Je savais que c'était ce que je voulais, mais je ne savais pas comment l'écrire. J'ai trouvé cette page et cela a aidé: http://www.codekeep.net/snippets/870c4ab3-419b-4dd2-a950-6d45beaf1295.aspx

36
mowwwalker

À mon humble avis, la réponse est légèrement meilleure car elle ne nécessite pas de nouvelle classe, rend les appels big-endian évidents et permet de mélanger les appels big-end et little-endian dans le flux.

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

Je ne connais pas STFS, mais changer l'endianisme est relativement facile. "Ordre réseau" est un gros endian, donc tout ce que vous avez à faire est de traduire du réseau en ordre hôte.

C'est facile car il y a déjà du code qui fait ça. Regarder IPAddress.NetworkToHostOrder, comme expliqué ici: ntohs () et ntohl () équivalent?

7
zmbq

À mon avis, vous voulez être prudent en faisant cela. La raison pour laquelle vous voudriez convertir de BigEndian en LittleEndian est si les octets lus sont en BigEndian et que le système d'exploitation qui les calcule fonctionne en LittleEndian.

C # n'est plus un langage uniquement pour les fenêtres. Avec des ports comme Mono, et également d'autres plates-formes Microsoft comme Windows Phone 7/8, Xbox 360/Xbox One, Windwos CE, Windows 8 Mobile, Linux With MONO, Apple with MONO, etc. It il est tout à fait possible que la plate-forme d'exploitation soit en BigEndian, auquel cas vous vous feriez mal si vous convertissiez le code sans effectuer de vérification.

Le BitConverter possède déjà un champ appelé "IsLittleEndian", vous pouvez l'utiliser pour déterminer si l'environnement d'exploitation est en LittleEndian ou non. Ensuite, vous pouvez effectuer l'inversion de façon conditionnelle.

En tant que tel, je viens d'écrire quelques extensions [] au lieu de créer une grande classe:

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

Imaginez donc cet exemple de 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
5
Ryan Mann

Vérification de la source de vérité (le code source) Je vois BinaryWriter comme l'écriture de données au format big endian. Dans BinaryReader, il suppose un format big endian et lit les données de manière appropriée dans un int, ce qui devrait fonctionner indépendamment de l'architecture du CPU. Vous devriez pouvoir écrire sur du gros ou du petit endian et lire sur l'autre architecture CPU sans code supplémentaire. Il est possible que ce soit nouveau à partir du noyau .NET ...

Donc, si votre fichier/flux source est en gros endian, vous devriez pouvoir utiliser BinaryReader pour lire les données sans code supplémentaire.

La source:

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

S'il vous plaît, quelqu'un intervient si je me trompe ou si je manque quelque chose.

0
jjxtra