2011-12-23 7 views
21

Sto cercando di migliorare la mia comprensione del formato di file STFS utilizzando un programma per leggere tutti i diversi bit di informazioni. Usando un sito Web con un riferimento di cui gli offset contengono informazioni, ho scritto un codice che ha un lettore binario che passa attraverso il file e inserisce i valori nelle variabili corrette.C# - Lettore binario in Big Endian?

Il problema è che tutti i dati sono SUPPOSTI a essere Big Endian e tutto ciò che il lettore binario legge è Little Endian. Quindi, qual è il modo migliore per risolvere questo problema?

È possibile creare una classe mimica di lettore binario che restituisce una matrice inversa di byte? C'è qualcosa che posso cambiare in istanza di classe che lo farà leggere in big endian quindi non devo riscrivere tutto?

Qualsiasi aiuto è apprezzato.

modifica: Ho provato ad aggiungere Encoding.BigEndianUnicode come parametro, ma legge ancora little endian.

+4

Skeet ha scritto uno: http: // www.yoda.arachsys.com/csharp/miscutil/ –

+0

@HansPassant, questa sarà una di quelle DLL che mi richiedono di rendere il mio codice open source? Perché alcune dll lo richiedono? – mowwwalker

+0

Walkerneo Ho cancellato la mia risposta perché zmbq ha risposto essenzialmente la stessa cosa 3 minuti prima di me. Il concetto di endianness non si applica agli array di byte, solo a parole, dwords, qwords, ecc., Cioè a gruppi di 2, 4, 8 e così via byte. Mi dispiace se significherebbe cambiare un sacco di codice, ma un uomo deve fare quello che un uomo deve fare. –

risposta

31

Io non sono di solito a rispondere alle mie domande, ma ho realizzato esattamente quello che volevo con qualche semplice codice:

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

} 

sapevo che è quello che Volevo, ma non sapevo come scriverlo. Ho trovato questa pagina e mi ha aiutato: http://www.codekeep.net/snippets/870c4ab3-419b-4dd2-a950-6d45beaf1295.aspx

+12

Fuori tema, ma i campi della classe ('a16' ecc.) Non sono necessari. Assegnate loro una matrice durante la costruzione, ma all'interno di ogni metodo sostituite quella matrice con una nuova matrice restituita dalla funzione 'Leggi'. Potresti semplicemente mettere "var a32 = base.ReadBytes ..." in ogni metodo e sbarazzarti dei campi. –

+15

Non sono inutili, sono dannosi. Trasformando ciò che è (potenzialmente, ignorando la condivisione del flusso sottostante) un codice thread-safe in una situazione di stato condivisa. – skolima

+4

Probabilmente vorrete controllare 'BitConverter.IsLittleEndian' prima di invertire. Se è 'falso' non è necessario invertire. –

5

Non ho familiarità con STFS, ma cambiare l'endianità è relativamente facile. "Ordine di rete" è big endian, quindi tutto ciò che devi fare è tradurre dalla rete all'ordine host.

Questo è facile perché c'è già il codice che lo fa. Guardate IPAddress.NetworkToHostOrder, come spiegato qui: ntohs() and ntohl() equivalent?

9

IMHO una risposta leggermente migliore in quanto non richiede che una classe diversa venga aggiornata, rende le chiamate big-endian ovvie e consente chiamate big e little-endian a essere mescolato nel flusso.

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

Ricordarsi di selezionare 'BitConverter.IsLittleEndian' prima di invertire. –

4

A mio parere, si vuole fare attenzione facendo questo. Il motivo per cui si vorrebbe convertire da BigEndian a LittleEndian è se i byte letti sono in BigEndian e il calcolo del sistema operativo contro di essi funziona in LittleEndian.

C# non è più una lingua di sola finestra. Con porte come Mono, e anche altre piattaforme Microsoft come Windows Phone 7/8, Xbox 360/Xbox One, Windwos CE, Windows 8 Mobile, Linux con MONO, Apple con MONO, ecc. È possibile che la piattaforma operativa sia in BigEndian, nel qual caso ti vergognerai se hai convertito il codice senza fare alcun controllo.

Il BitConverter dispone già di un campo denominato "IsLittleEndian" che è possibile utilizzare per determinare se l'ambiente operativo è in LittleEndian oppure no. Quindi puoi fare l'inversione in modo condizionale.

Come tale, in realtà ho appena scritto alcuni byte [] estensioni, invece di fare una 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; 
    } 

Quindi immaginate questo codice di esempio:

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 
Problemi correlati