2015-01-29 16 views
5

Sono un esperto sviluppatore di Python e ho imparato ad amare molte delle sue comodità. Ho conosciuto C# per un po 'di tempo ma di recente sono entrato in un codice più avanzato.Equivale in C# di "struct.pack/unpack" di Python?

Quello che mi chiedo è se c'è un modo per "analizzare" un array di byte in C# in un insieme di elementi (di dimensioni diverse).

Immaginiamo di avere questo:

Python:

import struct 
byteArray = "\xFF\xFF\x00\x00\x00\xFF\x01\x00\x00\x00" 
numbers = struct.unpack("<LHL",byteArray) 
print numbers[0] # 65535 
print numbers[1] # 255 
print numbers[2] # 1 

newNumbers = [0, 255, 1023] 
byteArray = struct.pack("<HHL",newNumbers) 
print byteArray # '\x00\x00\xFF\x00\xFF\x03\x00\x00' 

voglio ottenere lo stesso effetto in C#, senza ricorrere a enormi quantità disordinato di codice come questo:

C#:

byte[] byteArray = new byte[] { 255, 255, 0, 0, 0, 255, 1, 0, 0, 0 }; 
byte[] temp; 

int[] values = new int[3]; 

temp = new byte[4]; 
Array.Copy(byteArray, 0, temp, 0, 4); 
values[0] = BitConverter.ToInt32(temp); 

temp = new byte[2]; 
Array.Copy(byteArray, 4, temp, 0, 2); 
values[1] = BitConverter.ToInt16(temp); 

temp = new byte[4]; 
Array.Copy(byteArray, 8, temp, 0, 4); 
values[2] = BitConverter.ToInt32(temp); 

// Now values contains an array of integer values. 
// It would be OK to assume a common maximum (e.g. Int64) and just cast up to that, 
// but we still have to consider the size of the source bytes. 

// Now the other way. 
int[] values = new int[] { 0, 255, 1023 }; 
byteArray = new byte[8]; 

temp = BitConverter.GetBytes(values[0]); 
Array.Copy(temp,2,byteArray,0,2); 

temp = BitConverter.GetBytes(values[1]); 
Array.Copy(temp,2,byteArray,2,2); 

temp = BitConverter.GetBytes(values[2]); 
Array.Copy(temp,0,byteArray,4,4); 

Ovviamente il codice C# che ho è molto specifico e non in alcun modo veramente riutilizzabile.

Consigli?

+0

Sono curioso anche dell'esecuzione delle varie tecniche. – gbronner

risposta

5

Ho finito per scrivere la mia classe per gestire questo. È piuttosto complesso, ma sembra funzionare. È anche incompleto, ma funziona per quello di cui ho bisogno a questo punto. Sentiti libero di usarlo, e se ci sono dei buoni miglioramenti, ti prego di farmelo sapere.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Diagnostics; 

// This is a crude implementation of a format string based struct converter for C#. 
// This is probably not the best implementation, the fastest implementation, the most bug-proof implementation, or even the most functional implementation. 
// It's provided as-is for free. Enjoy. 

public class StructConverter 
{ 
    // We use this function to provide an easier way to type-agnostically call the GetBytes method of the BitConverter class. 
    // This means we can have much cleaner code below. 
    private static byte[] TypeAgnosticGetBytes(object o) 
    { 
     if (o is int) return BitConverter.GetBytes((int)o); 
     if (o is uint) return BitConverter.GetBytes((uint)o); 
     if (o is long) return BitConverter.GetBytes((long)o); 
     if (o is ulong) return BitConverter.GetBytes((ulong)o); 
     if (o is short) return BitConverter.GetBytes((short)o); 
     if (o is ushort) return BitConverter.GetBytes((ushort)o); 
     if (o is byte || o is sbyte) return new byte[] { (byte)o }; 
     throw new ArgumentException("Unsupported object type found"); 
    } 

    private static string GetFormatSpecifierFor(object o) 
    { 
     if (o is int) return "i"; 
     if (o is uint) return "I"; 
     if (o is long) return "q"; 
     if (o is ulong) return "Q"; 
     if (o is short) return "h"; 
     if (o is ushort) return "H"; 
     if (o is byte) return "B"; 
     if (o is sbyte) return "b"; 
     throw new ArgumentException("Unsupported object type found"); 
    } 

    /// <summary> 
    /// Convert a byte array into an array of objects based on Python's "struct.unpack" protocol. 
    /// </summary> 
    /// <param name="fmt">A "struct.pack"-compatible format string</param> 
    /// <param name="bytes">An array of bytes to convert to objects</param> 
    /// <returns>Array of objects.</returns> 
    /// <remarks>You are responsible for casting the objects in the array back to their proper types.</remarks> 
    public static object[] Unpack(string fmt, byte[] bytes) 
    { 
     Debug.WriteLine("Format string is length {0}, {1} bytes provided.", fmt.Length, bytes.Length); 

     // First we parse the format string to make sure it's proper. 
     if (fmt.Length < 1) throw new ArgumentException("Format string cannot be empty."); 

     bool endianFlip = false; 
     if (fmt.Substring(0, 1) == "<") 
     { 
      Debug.WriteLine(" Endian marker found: little endian"); 
      // Little endian. 
      // Do we need to flip endianness? 
      if (BitConverter.IsLittleEndian == false) endianFlip = true; 
      fmt = fmt.Substring(1); 
     } 
     else if (fmt.Substring(0, 1) == ">") 
     { 
      Debug.WriteLine(" Endian marker found: big endian"); 
      // Big endian. 
      // Do we need to flip endianness? 
      if (BitConverter.IsLittleEndian == true) endianFlip = true; 
      fmt = fmt.Substring(1); 
     } 

     // Now, we find out how long the byte array needs to be 
     int totalByteLength = 0; 
     foreach (char c in fmt.ToCharArray()) 
     { 
      Debug.WriteLine(" Format character found: {0}", c); 
      switch (c) 
      { 
       case 'q': 
       case 'Q': 
        totalByteLength += 8; 
        break; 
       case 'i': 
       case 'I': 
        totalByteLength += 4; 
        break; 
       case 'h': 
       case 'H': 
        totalByteLength += 2; 
        break; 
       case 'b': 
       case 'B': 
       case 'x': 
        totalByteLength += 1; 
        break; 
       default: 
        throw new ArgumentException("Invalid character found in format string."); 
      } 
     } 

     Debug.WriteLine("Endianness will {0}be flipped.", (object) (endianFlip == true ? "" : "NOT ")); 
     Debug.WriteLine("The byte array is expected to be {0} bytes long.", totalByteLength); 

     // Test the byte array length to see if it contains as many bytes as is needed for the string. 
     if (bytes.Length != totalByteLength) throw new ArgumentException("The number of bytes provided does not match the total length of the format string."); 

     // Ok, we can go ahead and start parsing bytes! 
     int byteArrayPosition = 0; 
     List<object> outputList = new List<object>(); 
     byte[] buf; 

     Debug.WriteLine("Processing byte array..."); 
     foreach (char c in fmt.ToCharArray()) 
     { 
      switch (c) 
      { 
       case 'q': 
        outputList.Add((object)(long)BitConverter.ToInt64(bytes,byteArrayPosition)); 
        byteArrayPosition+=8; 
        Debug.WriteLine(" Added signed 64-bit integer."); 
        break; 
       case 'Q': 
        outputList.Add((object)(ulong)BitConverter.ToUInt64(bytes,byteArrayPosition)); 
        byteArrayPosition+=8; 
        Debug.WriteLine(" Added unsigned 64-bit integer."); 
        break; 
       case 'l': 
        outputList.Add((object)(int)BitConverter.ToInt32(bytes, byteArrayPosition)); 
        byteArrayPosition+=4; 
        Debug.WriteLine(" Added signed 32-bit integer."); 
        break; 
       case 'L': 
        outputList.Add((object)(uint)BitConverter.ToUInt32(bytes, byteArrayPosition)); 
        byteArrayPosition+=4; 
        Debug.WriteLine(" Added unsignedsigned 32-bit integer."); 
        break; 
       case 'h': 
        outputList.Add((object)(short)BitConverter.ToInt16(bytes, byteArrayPosition)); 
        byteArrayPosition += 2; 
        Debug.WriteLine(" Added signed 16-bit integer."); 
        break; 
       case 'H': 
        outputList.Add((object)(ushort)BitConverter.ToUInt16(bytes, byteArrayPosition)); 
        byteArrayPosition += 2; 
        Debug.WriteLine(" Added unsigned 16-bit integer."); 
        break; 
       case 'b': 
        buf = new byte[1]; 
        Array.Copy(bytes,byteArrayPosition,buf,0,1); 
        outputList.Add((object)(sbyte)buf[0]); 
        byteArrayPosition++; 
        Debug.WriteLine(" Added signed byte"); 
        break; 
       case 'B': 
        buf = new byte[1]; 
        Array.Copy(bytes, byteArrayPosition, buf, 0, 1); 
        outputList.Add((object)(byte)buf[0]); 
        byteArrayPosition++; 
        Debug.WriteLine(" Added unsigned byte"); 
        break; 
       case 'x': 
        byteArrayPosition++; 
        Debug.WriteLine(" Ignoring a byte"); 
        break; 
       default: 
        throw new ArgumentException("You should not be here."); 
      } 
     } 
     return outputList.ToArray(); 
    } 

    /// <summary> 
    /// Convert an array of objects to a byte array, along with a string that can be used with Unpack. 
    /// </summary> 
    /// <param name="items">An object array of items to convert</param> 
    /// <param name="LittleEndian">Set to False if you want to use big endian output.</param> 
    /// <param name="NeededFormatStringToRecover">Variable to place an 'Unpack'-compatible format string into.</param> 
    /// <returns>A Byte array containing the objects provided in binary format.</returns> 
    public static byte[] Pack(object[] items, bool LittleEndian, out string NeededFormatStringToRecover) 
    { 

     // make a byte list to hold the bytes of output 
     List<byte> outputBytes = new List<byte>(); 

     // should we be flipping bits for proper endinanness? 
     bool endianFlip = (LittleEndian != BitConverter.IsLittleEndian); 

     // start working on the output string 
     string outString = (LittleEndian == false ? ">" : "<"); 

     // convert each item in the objects to the representative bytes 
     foreach (object o in items) 
     { 
      byte[] theseBytes = TypeAgnosticGetBytes(o); 
      if (endianFlip == true) theseBytes = (byte[])theseBytes.Reverse(); 
      outString += GetFormatSpecifierFor(o); 
      outputBytes.AddRange(theseBytes); 
     } 

     NeededFormatStringToRecover = outString; 

     return outputBytes.ToArray(); 

    } 

    public static byte[] Pack(object[] items) 
    { 
     string dummy = ""; 
     return Pack(items, true, out dummy); 
    } 
} 
+0

Traduzione impressionante. Solo un punto per considerare i casi i e I nel primo ciclo dovrebbero essere parte dei casi l e L nel secondo ciclo – Bryida

1

.NET (e quindi, C#) ha i metodi Marshal.StructureToPtr e Marshal.PtrToStructure.

È possibile utilizzarli per trasferire la memoria non elaborata su un struct come in C, non che io consiglierei di farlo in questo modo (poiché non è esattamente portatile). È inoltre necessario per ottenere il buffer gamma Byte[] nel mucchio nativo al fine di eseguire l'operazione su di esso:

T FromBuffer<T>(Byte[] buffer) where T : struct { 

    T temp = new T(); 
    int size = Marshal.SizeOf(temp); 
    IntPtr ptr = Marshal.AllocHGlobal(size); 

    Marshal.Copy(buffer, 0, ptr, size); 

    T ret = (T)Marshal.PtrToStructure(ptr, temp.GetType()); 
    Marshal.FreeHGlobal(ptr); 

    return ret; 
} 
1

BinaryWriter e BinaryReader invierà elementi arbitrari ad un array di byte o leggere gli elementi arbitrari da un array di byte

var str = new MemoryStream(); 
var bw = new BinaryWriter(str); 
bw.Write(42); 
bw.Write("hello"); 
... 
var bytes = str.ToArray(); 
+0

'BinaryReader | Writer' non supporta la scrittura/lettura di strutture, solo primitive, stringhe e array di byte. – Dai

+0

- ma fa esattamente ciò che vuole ed è l'equivalente logico C# del pacchetto/decompressione – pm100