Как преобразовать структуру в байтовый массив в C #? - PullRequest
71 голосов
/ 19 июля 2010

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

В моем методе main я создаю его экземпляр и присваиваю ему значения:

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

Каким будет фрагмент кода?

Ответы [ 13 ]

109 голосов
/ 19 июля 2010

Это довольно просто, используя сортировку.

Начало файла

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

27 голосов
/ 04 июня 2014

Если вы действительно хотите, чтобы он был БЫСТРОМ, вы можете сделать это с помощью небезопасного кода с CopyMemory. CopyMemory работает примерно в 5 раз быстрее (например, 800 МБ данных требуется 3 с для копирования с помощью маршаллинга, в то время как копирование с помощью CopyMemory занимает всего 6 секунд). Этот метод ограничивает вас использованием только тех данных, которые фактически хранятся в самом struct 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;
    }
21 голосов
/ 19 июля 2010

Посмотрите на эти методы:

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

Это бесстыдная копия другого потока, который я нашел при поиске!

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

14 голосов
/ 01 марта 2016

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

5 голосов
/ 15 мая 2015

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

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

2 голосов
/ 19 июля 2010

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

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

1 голос
/ 18 октября 2017

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

1 голос
/ 17 декабря 2013

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

Определите вашу структуру явно с [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);
0 голосов
/ 27 февраля 2017

Этот пример здесь применим только к чистым 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.

Однако, поскольку ОП хочет знать, как преобразовать саму структуру, наша структура 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();
0 голосов
/ 08 марта 2015
        Header header = new Header();
        Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
        Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);

Это должно быстро сработать, верно?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...