我是一名经验丰富的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#中实现相同的效果,而不是像这样使用庞大,混乱的代码:
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);
显然,我所拥有的C#代码是非常特定的,并且不以任何方式真正可重复使用。
么?
答案 0 :(得分:8)
我最后编写了自己的类来处理这个问题。它非常复杂,但似乎确实有效。它也不完整,但它适用于我现在需要的东西。随意使用它,如果有任何好的改进,请告诉我。
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);
}
}
答案 1 :(得分:2)
BinaryWriter和BinaryReader将任意项发送到字节数组或从字节数组中读取任意项
var str = new MemoryStream();
var bw = new BinaryWriter(str);
bw.Write(42);
bw.Write("hello");
...
var bytes = str.ToArray();
答案 2 :(得分:1)
.NET(以及C#)具有Marshal.StructureToPtr
和Marshal.PtrToStructure
方法。
您可以滥用这些内容将原始内存转换为{C}中的struct
,而不是我建议这样做(因为它不是完全可移植的)。您还需要将Byte[]
数组缓冲区放入本机堆中,以便对其执行操作:
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;
}