Python的C#';s";结构打包/解包“;?

Python的C#';s";结构打包/解包“;?,c#,byte,converter,C#,Byte,Converter,我是一名经验丰富的Python开发人员,并开始喜欢它的许多便利之处。事实上,我了解C#已经有一段时间了,但最近我开始学习一些更高级的编码 我想知道的是,是否有办法将C#中的字节数组“解析”为一组(大小不同)的项 想象一下我们有这样的情况: Python: import struct byteArray = "\xFF\xFF\x00\x00\x00\xFF\x01\x00\x00\x00" numbers = struct.unpack("<LHL",byteArray) print nu

我是一名经验丰富的Python开发人员,并开始喜欢它的许多便利之处。事实上,我了解C#已经有一段时间了,但最近我开始学习一些更高级的编码

我想知道的是,是否有办法将C#中的字节数组“解析”为一组(大小不同)的项

想象一下我们有这样的情况:

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'
很明显,我的C#代码非常具体,没有任何真正的可重用性

Advice?

.NET(因此,C#)具有
封送.StructureToPtr
封送.PtrToStructure
方法

您可以像在C语言中一样,滥用这些命令将原始内存强制转换为
struct
,但我不建议这样做(因为它不是完全可移植的)。您还需要将
字节[]
数组缓冲区放入本机堆,以便对其执行操作:

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;
}
T FromBuffer(字节[]buffer),其中T:struct{
T temp=新的T();
int size=Marshal.SizeOf(临时);
IntPtr ptr=Marshal.AllocHGlobal(大小);
封送副本(缓冲区、0、ptr、大小);
T ret=(T)Marshal.PtrToStructure(ptr,temp.GetType());
弗里赫全球元帅(ptr);
返回ret;
}

BinaryWriter和BinaryReader将向字节数组发送任意项或从字节数组读取任意项

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

我最终写了自己的类来处理这个问题。这相当复杂,但似乎确实有效。它也是不完整的,但它在这一点上满足了我的需要。请随意使用,如果有任何好的改进,请告诉我

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);
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统诊断;
//这是C#的基于格式字符串的结构转换器的粗略实现。
//这可能不是最好的实现、最快的实现、最防bug的实现,甚至不是最实用的实现。
//它是免费提供的。享受。
公共类结构转换器
{
//我们使用此函数提供了一种更简单的方法,可以不依赖于类型地调用BitConverter类的GetBytes方法。
//这意味着我们可以在下面有更干净的代码。
私有静态字节[]类型不可知GetBytes(对象o)
{
if(o是int)返回BitConverter.GetBytes((int)o);
if(o是uint)返回BitConverter.GetBytes((uint)o);
如果(o长),则返回BitConverter.GetBytes((长)o);
if(o是ulong)返回BitConverter.GetBytes((ulong)o);
如果(o短)返回BitConverter.GetBytes((短)o);
如果(o是ushort)返回BitConverter.GetBytes((ushort)o);
如果(o是byte | | o是sbyte),则返回新的byte[]{(byte)o};
抛出新ArgumentException(“找到不支持的对象类型”);
}
私有静态字符串GetFormatSpecifierFor(对象o)
{
如果(o为int)返回“i”;
如果(o为uint)返回“I”;
如果(o长)返回“q”;
如果(o为ulong)返回“Q”;
如果(o短)返回“h”;
如果(o为ushort)返回“H”;
如果(o是字节),则返回“B”;
如果(o是sbyte)返回“b”;
抛出新ArgumentException(“找到不支持的对象类型”);
}
/// 
///基于Python的“struct.unpack”协议将字节数组转换为对象数组。
/// 
///与“struct.pack”兼容的格式字符串
///要转换为对象的字节数组
///对象数组。
///您负责将数组中的对象强制转换回其正确的类型。
公共静态对象[]解包(字符串fmt,字节[]字节)
{
WriteLine(“格式字符串为长度{0},提供了{1}字节。”,fmt.length,bytes.length);
//首先,我们分析格式字符串以确保其正确性。
如果(fmt.Length<1)抛出新的ArgumentException(“格式字符串不能为空”);
bool-endianFlip=false;
if(fmt.子字符串(0,1)==“”)
{
WriteLine(“找到了Endian标记:big Endian”);
//大恩迪安。
//我们需要翻转endianness吗?
如果(BitConverter.IsLittleEndian==true)endianFlip=true;
fmt=fmt.子串(1);
}
//现在,我们来看看字节数组需要多长时间
int TOTALL BYTELENGTH=0;
foreach(fmt.ToCharArray()中的字符c)
{
WriteLine(“找到的格式字符:{0}”,c);
开关(c)
{
案例‘q’:
案例‘Q’:
总长度+=8;
打破
案例“i”:
案例“I”:
总BYTELENGTH+=4;
打破
案例“h”:
案例“H”:
总BYTELENGTH+=2;
打破
案例“b”:
案例“B”:
案例“x”:
TotalByTeleLength+=1;
打破
违约:
抛出新ArgumentException(“在格式字符串中找到无效字符”);
}
}
WriteLine(“Endianness将{0}翻转。”,(object)(endianFlip==true?”:“NOT”);
WriteLine(“字节数组的长度应为{0}字节。”,totalByTeleLength);
//测试字节数组长度,查看它是否包含字符串所需的字节数。
如果(bytes.Length!=totalbytellength)抛出新的ArgumentException(“提供的字节数与格式字符串的总长度不匹配”);
//好的,我们可以开始解析字节了!
int byteArrayPosition=0;
List outputList=新列表();
字节[]buf;
WriteLine(“处理字节数组…”);
foreach(fmt.ToCharArray()中的字符c)
{
开关(c)
{
案例‘q’:
将((对象)(长)位转换器.ToInt64(字节,byteArrayPosition));
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);
    }
}