Как преобразовать структуру в массив байтов в C #?

83

Как преобразовать структуру в массив байтов в 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);
}

Каким должен быть фрагмент кода?

Свапнил Гупта
источник
Одно исправление в последней строке MyPing.Send (в качестве параметра принимает байтовый массив); Это Отправить, а не Отправить ...
Swapnil Gupta
Привет, Петар, я тебя не
понял
3
Было бы неплохо принять некоторые ответы на ваши предыдущие вопросы.
jnoss
1
Я подозреваю, что было бы лучше уточнить ожидаемый результат; есть много способов превратить это в байт [] ... Мы, вероятно, можем сделать некоторые предположения относительно большей части этого, что вам нужны представления полей фиксированного размера с сетевым порядком байтов - но как насчет Струна?
Марк Грейвелл
Позаботьтесь о порядке прямого и обратного порядка байтов и примерно 32/64 бита, если вы выберете параметр Маршалла.
x77

Ответы:

127

Это довольно просто, используя маршалинг.

Начало файла

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

Винсент Макнабб
источник
Спасибо, Винсет. GetBytes () следует вызывать после отправки байта [] ?? а метод frombytes () отправляет байты? Я немного запутался, приятель?
Swapnil Gupta
1
GetBytes преобразует вашу структуру в массив. FromBytes преобразует байты обратно в вашу структуру. Это видно по сигнатурам функций.
Винсент Макнабб
1
@Swapnil Это еще один вопрос, который вам следует задать отдельно. Вам следует подумать о завершении пары руководств CE по сокетам. Просто выполните поиск в Google.
Винсент Макнабб
3
В вашем методе fromBytes нет необходимости выделять CIFSPacket дважды. Marshal.SizeOf с радостью примет Type в качестве параметра, а Marshal.PtrToStructure выделяет новый управляемый объект.
Джек Уклея
1
Обратите внимание, что в некоторых случаях функция «StructureToPtr» выдает исключение. Это можно исправить, передав «false» вместо «true» Marshal.StructureToPtr(str, ptr, false);. Но нужно упомянуть, что я использую функции, обернутые в универсальный, хотя…
Привет-Ангел
30

Если вы действительно хотите, чтобы это было БЫСТРО в Windows, вы можете сделать это, используя небезопасный код с CopyMemory. CopyMemory примерно в 5 раз быстрее (например, для копирования 800 МБ данных с помощью маршаллинга требуется 3 секунды, а для копирования с помощью CopyMemory требуется всего 0,6 секунды). Этот метод ограничивает использование только данных, которые фактически хранятся в самой структуре blob, например чисел или байтовых массивов фиксированной длины.

    [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;
    }
Павел
источник
4
В качестве предупреждения тем, кто читает этот ответ .. Это не кроссплатформенный подход (он использует только kernel32.dll для Windows). Но опять же, это было написано в 2014 году. :)
Raid
2
Плюс требуют, чтобы структура была последовательной.
Tomer W
25

Взгляните на эти методы:

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!

Обновление : для получения дополнительной информации проверьте источник

Абдель Рауф
источник
У меня есть преобразованная структура в байтовый массив с использованием Marshalling, как я могу проверить, получаю ли я ответ от сокета? Как это проверить?
Swapnil Gupta
@Alastair, я пропустил это !! Спасибо за указание .. Я обновил свой ответ.
Abdel Raoof
2
Этот параметр зависит от платформы - позаботьтесь о Grand Endian и Little endian и о 32 битах / 64 битах.
x77
@Abdel, и -1 пропал :)
Аластер Питтс,
Имеет ли смысл выполнить Alloc, обернуть средний бит в try, а затем поместить Free в файл finally? Кажется маловероятным, что что-то выйдет из строя, но если это произойдет, освободится ли когда-нибудь память?
Кейси
18

Вариант кода 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().

ксанатос
источник
Если удалить, в where T : structпротивном случае будет жалоба на то, Tчто он не прошел non-nullable type.
codenamezero
GCHandle.Allocзавершится ошибкой, если структура содержит непреобразуемые данные, например массив
joe
@joe Вы правы. Код был написан для данной структуры, содержащей только непреобразуемые типы и string.
xanatos 01
5

Поскольку основной ответ заключается в использовании типа 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;
    }

Проверено, работают.

pbies
источник
4

Я знаю, что это действительно поздно, но с 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());
Варскотт128
источник
2

Вы можете использовать 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)

Затем вы выбираете элементы для сохранения / загрузки в поток -> байтовый массив.

x77
источник
1

Это можно сделать очень просто.

Определите вашу структуру явно с помощью [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);
DianeS
источник
При выполнении явных упорядоченных операций с коллекцией фиксированного размера вам, вероятно, следует использовать массив и цикл for. Массив из-за фиксированного размера и цикл for из-за того, что не гарантируется, что foreach будет в ожидаемом вами порядке, если вы не знаете базовую реализацию вашего типа списка и его перечислитель, и что он никогда не изменится. Например, можно определить перечислитель, чтобы он начинался с конца и двигался в обратном направлении.
1

Я придумал другой подход, который мог бы преобразовать любой 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. Мое вдохновение - от TLVType-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, также есть небольшие накладные расходы , но в остальном их не так много.

codenamezero
источник
6
Обычно, когда люди пытаются иметь дело с такими вещами, они также обеспокоены производительностью сериализации. Теоретически любой массив структур можно интерпретировать как байтовый массив без дорогостоящей сериализации и копирования.
Танвир Бадар
0

Я бы посмотрел на классы BinaryReader и BinaryWriter. Недавно мне пришлось сериализовать данные в байтовый массив (и обратно), и я нашел эти классы только после того, как я в основном их переписал сам.

http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx

На этой странице тоже есть хороший пример.

AndrewS
источник
0

Похоже на предопределенную структуру (уровень C) для какой-то внешней библиотеки. Маршал - твой друг. Проверьте:

http://geekswithblogs.net/taylorrich/archive/2006/08/21/88665.aspx

для начала как с этим бороться. Обратите внимание, что с помощью атрибутов вы можете определять такие вещи, как разметка байтов и обработка строк. ОЧЕНЬ хороший подход, на самом деле.

Ни BinaryFormatter, ни MemoryStream для этого не предназначены.

TomTom
источник
0

Ответ @Abdel Olakara не работает в .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);
    }
lsaturn
источник
0
        Header header = new Header();
        Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
        Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);

Это должно помочь быстро, не так ли?

Райан Браун
источник
Версия GCHandle намного лучше.
Петър Петров
0

Этот пример применим только к чистым непреобразуемым типам, например, к типам, которые могут быть запомнены непосредственно в 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();
Петър Петров
источник