Як перетворити структуру на байтовий масив у 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 (як параметр бере байтовий масив); Це Send, а не SendTo ......
Swapnil Gupta

Привіт Петре, я не зрозумів тебе ...
Swapnil Gupta

3
Можливо, було б добре прийняти деякі відповіді на ваші попередні запитання.
jnoss

1
Я підозрюю, що допомогло б бути дещо конкретнішим щодо результату, який ви очікуєте; Є безліч способів перетворити це на байт [] ... Ми, мабуть, можемо зробити деякі припущення щодо більшості з них, що ви хочете, щоб мережеві байтові порядки представляли поля впорядкованого поля - але як щодо рядок?
Марк Гравелл

Потурбуйтеся про Grand Endian та Little endian та про 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 немає необхідності виділяти пакет CIFSP двічі. Marshal.SizeOf з радістю прийме Type як параметр, а Marshal.PtrToStructure виділить новий керований об'єкт.
Джек Уклея

1
Зверніть увагу, що за деяких обставин функція «StructureToPtr» видає виняток. Це можна виправити, передавши «false» замість «true» Marshal.StructureToPtr(str, ptr, false);. Але потрібно згадати, що я використовую функції, перетворені на загальний, проте ...
Hi-Angel

30

Якщо ви дійсно хочете, щоб це було ШВИДКО в Windows, ви можете зробити це, використовуючи небезпечний код за допомогою CopyMemory. CopyMemory приблизно в 5 разів швидший (наприклад, 800 МБ даних займає 3 секунди для копіювання за допомогою маршалінгу, тоді як для копіювання через CopyMemory потрібно лише 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
Як увага тим, хто читає цю відповідь .. Це не зручно для різних платформ (у ньому використовується лише Windows kernel32.dll). Але знову ж таки, це було написано у 2014 році. :)
Рейд

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

Це безсоромна копія чергової нитки, яку я знайшов на Googling!

Оновлення : Щоб отримати докладнішу інформацію, перевірте джерело


Я конвертував структуру в байтовий масив за допомогою Marshalling зараз, як я можу перевірити, чи отримую я відповідь від сокета? Як це перевірити?
Swapnil Gupta

@Alastair, я це пропустив !! Дякую, що вказали. Я оновив свою відповідь.
Абдель Раоф,

2
Цей параметр залежить від платформи - подбайте про Grand Endian та Little endian та приблизно 32 Bits / 64 bits.
x77

@ Абдель, і -1 вже немає :)
Аластер Піттс,

Чи було б сенсом виконати Alloc, спробувати прокрутити середній біт, а потім покласти Free всередину нарешті? Здається малоймовірним, що щось не вдасться, але якщо це станеться, чи звільняється пам'ять?
Кейсі

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 Ви маєте рацію. Код був написаний для даної структури, яка містила лише просвічувані типи та string.
xanatos

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

Перевірено, вони працюють.


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

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)

Потім ви вибираєте членів для збереження / завантаження в потік -> байтовий масив.


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

Роблячи явно впорядковані операції над колекцією фіксованого розміру, ви, мабуть, повинні використовувати масив і for-loop. Масив, оскільки він має фіксований розмір, і цикл 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. Моє натхнення - TLVтип-довжина-значення. Давайте подивимось на код:

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для серіалізації невідомого розміру raw 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, є також невеликі накладні витрати , але крім цього, це не так вже й багато.


6
Як правило, коли люди намагаються мати справу з такими речами, вони також стурбовані результатами серіалізації. Теоретично будь-який масив структур може бути переосмислений як байтовий масив без залучення дорогої серіалізації та копіювання.
Tanveer Badar

0

Я б подивився на класи BinaryReader та BinaryWriter. Нещодавно мені довелося серіалізувати дані до байтового масиву (і назад), і я знайшов ці класи лише після того, як в основному переписав їх сам.

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

Є хороший приклад і на цій сторінці.


0

Виглядає як заздалегідь визначена структура (рівень С) для якоїсь зовнішньої бібліотеки. Маршал - твій друг. Перевірка:

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

для початку, як з цим боротися. Зверніть увагу, що ви можете - за допомогою атрибутів - визначати такі речі, як макет байтів та обробка рядків. ДУЖЕ приємний підхід, насправді.

Ні BinaryFormatter, ні MemoryStream для цього не зроблені.


0

Відповідь @Abdel Olakara 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);
    }

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();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.