Как преобразовать структуру в байтовый массив в C#?
Как мне преобразовать структуру в байтовый массив в C#?
Я определил структуру как это:
public struct CIFSPacket
{
public uint protocolIdentifier; //The value must be "0xFF+'SMB'".
public byte command;
public byte errorClass;
public byte reserved;
public ushort error;
public byte flags;
//Here there are 14 bytes of data which is used differently among different dialects.
//I do want the flags2. However, so I'll try parsing them.
public ushort flags2;
public ushort treeId;
public ushort processId;
public ushort userId;
public ushort multiplexId;
//Trans request
public byte wordCount;//Count of parameter words defining the data portion of the packet.
//From here it might be undefined...
public int parametersStartIndex;
public ushort byteCount; //Buffer length
public int bufferStartIndex;
public string Buffer;
}
В моем основном методе я создаю его экземпляр и присваиваю ему значения:
CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;
packet.Buffer = "NT LM 0.12";
Теперь я хочу отправить этот пакет по сокету. Для этого мне нужно преобразовать структуру в байтовый массив. Как мне это сделать?
Мой полный код выглядит следующим образом.
static void Main(string[] args)
{
Socket MyPing = new Socket(AddressFamily.InterNetwork,
SocketType.Stream , ProtocolType.Unspecified ) ;
MyPing.Connect("172.24.18.240", 139);
//Fake an IP Address so I can send with SendTo
IPAddress IP = new IPAddress(new byte[] { 172,24,18,240 });
IPEndPoint IPEP = new IPEndPoint(IP, 139);
//Local IP for Receiving
IPEndPoint Local = new IPEndPoint(IPAddress.Any, 0);
EndPoint EP = (EndPoint)Local;
CIFSPacket packet = new CIFSPacket();
packet.protocolIdentifier = 0xff;
packet.command = (byte)CommandTypes.SMB_COM_NEGOTIATE;
packet.errorClass = 0xff;
packet.error = 0;
packet.flags = 0x00;
packet.flags2 = 0x0001;
packet.multiplexId = 22;
packet.wordCount = 0;
packet.byteCount = 119;
packet.Buffer = "NT LM 0.12";
MyPing.SendTo(It takes byte array as parameter);
}
Каким будет фрагмент кода?
15 ответов
Это довольно легко, используя маршаллинг.
Начало файла
using System.Runtime.InteropServices
функция
byte[] getBytes(CIFSPacket str) {
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
И преобразовать его обратно:
CIFSPacket fromBytes(byte[] arr) {
CIFSPacket str = new CIFSPacket();
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, ptr, size);
str = (CIFSPacket)Marshal.PtrToStructure(ptr, str.GetType());
Marshal.FreeHGlobal(ptr);
return str;
}
В вашей структуре вам нужно поместить это перед строкой
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string Buffer;
И убедитесь, что SizeConst настолько большой, насколько это возможно.
И вам, вероятно, следует прочитать это: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx
Если вы действительно хотите, чтобы он был БЫСТРОМ, вы можете сделать это, используя небезопасный код с CopyMemory. CopyMemory примерно в 5 раз быстрее (например, 800 МБ данных требуется 3 с для копирования с помощью маршаллинга, в то время как копирование с помощью CopyMemory занимает всего 6 секунд). Этот метод ограничивает использование только тех данных, которые фактически хранятся в самом структурном объекте, например, числа или байтовые массивы фиксированной длины.
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
private static unsafe extern void CopyMemory(void *dest, void *src, int count);
private static unsafe byte[] Serialize(TestStruct[] index)
{
var buffer = new byte[Marshal.SizeOf(typeof(TestStruct)) * index.Length];
fixed (void* d = &buffer[0])
{
fixed (void* s = &index[0])
{
CopyMemory(d, s, buffer.Length);
}
}
return buffer;
}
Посмотрите на эти методы:
byte [] StructureToByteArray(object obj)
{
int len = Marshal.SizeOf(obj);
byte [] arr = new byte[len];
IntPtr ptr = Marshal.AllocHGlobal(len);
Marshal.StructureToPtr(obj, ptr, true);
Marshal.Copy(ptr, arr, 0, len);
Marshal.FreeHGlobal(ptr);
return arr;
}
void ByteArrayToStructure(byte [] bytearray, ref object obj)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray,0, i,len);
obj = Marshal.PtrToStructure(i, obj.GetType());
Marshal.FreeHGlobal(i);
}
Это бесстыдная копия другой ветки, которую я нашел на Google!
Обновление: для более подробной информации, проверьте источник
Вариант кода Vicent с одним меньшим выделением памяти:
public static byte[] GetBytes<T>(T str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
GCHandle h = default(GCHandle);
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
Marshal.StructureToPtr<T>(str, h.AddrOfPinnedObject(), false);
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return arr;
}
public static T FromBytes<T>(byte[] arr) where T : struct
{
T str = default(T);
GCHandle h = default(GCHandle);
try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}
return str;
}
я использую GCHandle
"закрепить" память, а затем я использую ее адрес с h.AddrOfPinnedObject()
,
Я знаю, что это действительно поздно, но с C# 7.3 вы можете сделать это для неуправляемых структур или чего-либо еще, что не связано (int, bool и т. Д.):
public static unsafe byte[] ConvertToBytes<T>(T value) where T : unmanaged {
byte* pointer = (byte*)&value;
byte[] bytes = new byte[sizeof(T)];
for (int i = 0; i < sizeof(T); i++) {
bytes[i] = pointer[i];
}
return bytes;
}
Затем используйте вот так:
struct MyStruct {
public int Value1;
public int Value2;
//.. blah blah blah
}
byte[] bytes = ConvertToBytes(new MyStruct());
Поскольку основным ответом является использование типа CIFSPacket, который недоступен (или более не доступен) в C#, я написал правильные методы:
static byte[] getBytes(object str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
static T fromBytes<T>(byte[] arr)
{
T str = default(T);
int size = Marshal.SizeOf(str);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, ptr, size);
str = (T)Marshal.PtrToStructure(ptr, str.GetType());
Marshal.FreeHGlobal(ptr);
return str;
}
Проверено, они работают.
Почти все ответы здесь используют
Marshal.StructureToPtr
, который может быть хорош для P / Invoke, но очень медленный и даже не всегда представляет фактическое необработанное содержимое значения. Ответ @Varscott128 намного лучше, но он также содержит явное копирование байтов, которое не обязательно.
Для неуправляемых структур (структур без управляемых ссылок) все, что вам нужно, - это переинтерпретировать выделенный массив результатов, чтобы простое присвоение помогло (работает даже для огромных структур):
.NET (Core) Решение:
Если вы можете использовать
Unsafe
class, то решение действительно простое. В
unsafe
модификатор требуется только из-за
sizeof(T)
.
public static unsafe byte[] SerializeValueType<T>(in T value) where T : unmanaged
{
byte[] result = new byte[sizeof(T)];
Unsafe.As<byte, T>(ref result[0]) = value;
return result;
}
// Note: Validation is omitted for simplicity
public static T DeserializeValueType<T>(byte[] data) where T : unmanaged
=> return Unsafe.As<byte, T>(ref data[0]);
.NET Framework / стандартное решение:
public static unsafe byte[] SerializeValueType<T>(in T value) where T : unmanaged
{
byte[] result = new byte[sizeof(T)];
fixed (byte* dst = result)
*(T*)dst = value;
return result;
}
// Note: Validation is omitted for simplicity
public static unsafe T DeserializeValueType<T>(byte[] data) where T : unmanaged
{
fixed (byte* src = data)
return *(T*)src;
}
См. Полный код с проверками здесь .
Примечания:
Пример OP содержит
string
, который является ссылочным типом, поэтому приведенное выше решение нельзя использовать для этого. И если вы не можете использовать общие методы по некоторым причинам вещи начинают становиться более сложным, особенно для .NET Framework (но расчет не унифицированная размер является боль также на платформе ядра). Если производительность не имеет значения, вы можете вернуться к
Marshal.SizeOf
а также
StructureToPtr
как предлагается несколькими другими ответами, или не стесняйтесь использовать BinarySerializer.SerializeValueType
из моей библиотеки, которую я также связал для приведенных выше примеров (NuGet).
Вы можете использовать Marshal (StructureToPtr, ptrToStructure) и Marshal.copy, но это зависит от формы.
Сериализация включает в себя функции пользовательской сериализации.
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
SerializationInfo включает функции для сериализации каждого члена.
BinaryWriter и BinaryReader также содержат методы для сохранения / загрузки в массив байтов (поток).
Обратите внимание, что вы можете создать MemoryStream из байтового массива или байтового массива из MemoryStream.
Вы можете создать метод Save и метод New в вашей структуре:
Save(Bw as BinaryWriter)
New (Br as BinaryReader)
Затем вы выбираете элементы для сохранения / загрузки в поток -> байтовый массив.
Я придумал другой подход, который может преобразовать любой struct
однако без проблем с фиксированной длиной результирующий байтовый массив будет иметь немного больше накладных расходов.
Вот образец struct
:
[StructLayout(LayoutKind.Sequential)]
public class HelloWorld
{
public MyEnum enumvalue;
public string reqtimestamp;
public string resptimestamp;
public string message;
public byte[] rawresp;
}
Как видите, все эти структуры потребуют добавления атрибутов фиксированной длины. Что часто заканчивалось тем, что занимало больше места, чем требовалось. Обратите внимание, что LayoutKind.Sequential
требуется, так как мы хотим, чтобы отражение всегда дает нам один и тот же порядок при FieldInfo
, Мое вдохновение исходит от TLV
Type-Length-Value. Давайте посмотрим на код:
public static byte[] StructToByteArray<T>(T obj)
{
using (MemoryStream ms = new MemoryStream())
{
FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo info in infos)
{
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream inms = new MemoryStream()) {
bf.Serialize(inms, info.GetValue(obj));
byte[] ba = inms.ToArray();
// for length
ms.Write(BitConverter.GetBytes(ba.Length), 0, sizeof(int));
// for value
ms.Write(ba, 0, ba.Length);
}
}
return ms.ToArray();
}
}
Вышеупомянутая функция просто использует BinaryFormatter
сериализовать сырой неизвестный размер object
и я просто отслеживаю размер и сохраняю его в выводе MemoryStream
тоже.
public static void ByteArrayToStruct<T>(byte[] data, out T output)
{
output = (T) Activator.CreateInstance(typeof(T), null);
using (MemoryStream ms = new MemoryStream(data))
{
byte[] ba = null;
FieldInfo[] infos = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
foreach (FieldInfo info in infos)
{
// for length
ba = new byte[sizeof(int)];
ms.Read(ba, 0, sizeof(int));
// for value
int sz = BitConverter.ToInt32(ba, 0);
ba = new byte[sz];
ms.Read(ba, 0, sz);
BinaryFormatter bf = new BinaryFormatter();
using (MemoryStream inms = new MemoryStream(ba))
{
info.SetValue(output, bf.Deserialize(inms));
}
}
}
}
Когда мы хотим преобразовать его обратно в исходный struct
мы просто читаем длину обратно и напрямую сбрасываем ее обратно в BinaryFormatter
которые в свою очередь сбрасывают его обратно в struct
,
Эти 2 функции являются общими и должны работать с любыми struct
Я протестировал приведенный выше код в моем C#
проект, где у меня есть сервер и клиент, подключен и общаться через NamedPipeStream
и я пересылаю struct
в виде байтового массива от одного к другому и преобразовал его обратно.
Я считаю, что мой подход может быть лучше, так как он не фиксирует длину на struct
Сам и единственные накладные расходы просто int
для каждого поля в вашей структуре. Есть также небольшие издержки внутри байтового массива, сгенерированного BinaryFormatter
, но кроме этого, это не так много.
Это можно сделать очень просто.
Определите вашу структуру явно с [StructLayout(LayoutKind.Explicit)]
int size = list.GetLength(0);
IntPtr addr = Marshal.AllocHGlobal(size * sizeof(DataStruct));
DataStruct *ptrBuffer = (DataStruct*)addr;
foreach (DataStruct ds in list)
{
*ptrBuffer = ds;
ptrBuffer += 1;
}
Этот код может быть написан только в небезопасном контексте. Вы должны освободить addr
когда вы закончите с этим.
Marshal.FreeHGlobal(addr);
Header header = new Header();
Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);
Это должно быстро сработать, верно?
Этот пример здесь применим только к чистым blittable типам, например, типам, которые могут быть memcpy'd непосредственно в C.
Пример - хорошо известная 64-битная структура
[StructLayout(LayoutKind.Sequential)]
public struct Voxel
{
public ushort m_id;
public byte m_red, m_green, m_blue, m_alpha, m_matid, m_custom;
}
Определенная точно так же, структура будет автоматически упакована как 64-битная.
Теперь мы можем создать объем вокселей:
Voxel[,,] voxels = new Voxel[16,16,16];
И сохраните их все в байтовом массиве:
int size = voxels.Length * 8; // Well known size: 64 bits
byte[] saved = new byte[size];
GCHandle h = GCHandle.Alloc(voxels, GCHandleType.Pinned);
Marshal.Copy(h.AddrOfPinnedObject(), saved, 0, size);
h.Free();
// now feel free to save 'saved' to a File / memory stream.
Однако, поскольку OP хочет знать, как преобразовать саму структуру, наша структура Voxel может иметь следующий метод ToBytes
:
byte[] bytes = new byte[8]; // Well known size: 64 bits
GCHandle h = GCHandle.Alloc(this, GCHandleType.Pinned);
Marshal.Copy(hh.AddrOfPinnedObject(), bytes, 0, 8);
h.Free();
Выглядит как предопределенная (уровень C) структура для некоторой внешней библиотеки. Маршал твой друг. Проверьте:
http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx
для начала, как с этим бороться. Обратите внимание, что вы можете - с атрибутами - определять такие вещи, как разметка байтов и обработка строк. ОЧЕНЬ хороший подход, на самом деле.
Ни BinaryFormatter, ни MemoryStream не сделаны для этого.
Я бы взглянул на классы BinaryReader и BinaryWriter. Недавно мне пришлось сериализовать данные в байтовый массив (и обратно), и я нашел эти классы только после того, как сам переписал их.
http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx
На этой странице также есть хороший пример.
@Abdel Olakara answer Donese не работает в.net 3.5, следует изменить, как показано ниже:
public static void ByteArrayToStructure<T>(byte[] bytearray, ref T obj)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
Marshal.Copy(bytearray, 0, i, len);
obj = (T)Marshal.PtrToStructure(i, typeof(T));
Marshal.FreeHGlobal(i);
}