2015-01-29 12 views
5

Ich bin ein erfahrener Python-Entwickler und habe viele seiner Annehmlichkeiten lieben gelernt. Ich kenne C# schon seit einiger Zeit, bin aber in letzter Zeit auf etwas fortgeschrittenere Programmierung gestoßen.Äquivalent in C# von Pythons "struct.pack/unpack"?

Ich frage mich, ob es eine Möglichkeit gibt, ein Byte-Array in C# in eine Menge von (unterschiedlich großen) Elementen zu "parsen".

Stellen haben wir diese:

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' 

Ich mag die gleiche Wirkung in C# erreichen, ohne so zu riesigen, unordentlichen Mengen von Code zurückgreifen:

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

Offensichtlich ist der C# -Code, den ich habe, sehr spezifisch und in keiner Weise wirklich wiederverwendbar.

Beratung?

+0

Ich bin neugierig auf die Leistung der verschiedenen Techniken. – gbronner

Antwort

5

Ich schrieb meine eigene Klasse, um damit umzugehen. Es ist ziemlich komplex, aber es scheint zu funktionieren. Es ist auch unvollständig, aber es funktioniert für das, was ich an diesem Punkt brauche. Fühlen Sie sich frei, es zu benutzen, und wenn es irgendwelche guten Verbesserungen gibt, lass es mich wissen.

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

Awesome Übersetzung. Nur ein Punkt, um die Fälle I und I in der ersten Schleife zu betrachten, sollte Teil der Fälle I und L in der zweiten Schleife sein – Bryida

1

.NET (und damit C#) verfügt über die Methoden Marshal.StructureToPtr und Marshal.PtrToStructure.

Sie können diese verwenden, um rohen Speicher auf eine struct wie in C zu werfen, nicht, dass ich es auf diese Weise empfehlen würde (wie es nicht gerade portabel ist). Sie müssen auch Ihre Byte[] Array-Puffer in den nativen Haufen erhalten, um den Betrieb auf sie auszuführen:

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

Binary und Binary werden beliebige Elemente zu einem Byte-Array senden oder beliebige Elemente aus einem Byte-Array lesen

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

'BinaryReader | Writer' unterstützt das Schreiben/Lesen von Strukturen, nur Primitiven, Strings und Byte-Arrays. – Dai

+0

in der Tat - aber ist genau das, was er will und ist die C# logische Entsprechung der Pack/Unpack – pm100

Verwandte Themen