Читання структури даних C / C ++ у C # з байтового масиву


85

Який найкращий спосіб заповнити структуру C # із байтового масиву [], де дані були зі структури C / C ++? Структура C буде виглядати приблизно так (мій C дуже іржавий):

typedef OldStuff {
    CHAR Name[8];
    UInt32 User;
    CHAR Location[8];
    UInt32 TimeStamp;
    UInt32 Sequence;
    CHAR Tracking[16];
    CHAR Filler[12];
}

І заповнив би щось подібне:

[StructLayout(LayoutKind.Explicit, Size = 56, Pack = 1)]
public struct NewStuff
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    [FieldOffset(0)]
    public string Name;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(8)]
    public uint User;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    [FieldOffset(12)]
    public string Location;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(20)]
    public uint TimeStamp;

    [MarshalAs(UnmanagedType.U4)]
    [FieldOffset(24)]
    public uint Sequence;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
    [FieldOffset(28)]
    public string Tracking;
}

Що кращий спосіб скопіювати OldStuffв NewStuff, якщо OldStuffбув прийнятий в якості байт [] масив?

В даний час я роблю щось на зразок наступного, але це відчуває себе якось незграбно.

GCHandle handle;
NewStuff MyStuff;

int BufferSize = Marshal.SizeOf(typeof(NewStuff));
byte[] buff = new byte[BufferSize];

Array.Copy(SomeByteArray, 0, buff, 0, BufferSize);

handle = GCHandle.Alloc(buff, GCHandleType.Pinned);

MyStuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NewStuff));

handle.Free();

Чи є кращий спосіб досягти цього?


Чи могло б використання BinaryReaderкласу збільшити продуктивність порівняно із закріпленням пам'яті та використанням Marshal.PtrStructure?


1
FYI, якщо ваша програма працює на різних машинах, вам може знадобитися обробляти мало проти великого ендіана.
KPexEA,

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

Відповіді:


114

З того, що я бачу в цьому контексті, вам не потрібно копіювати SomeByteArrayв буфер. Вам просто потрібно дістати ручку SomeByteArray, закріпити її, скопіювати IntPtrдані за допомогою, PtrToStructureа потім відпустити. Не потрібно копії.

Це було б:

NewStuff ByteArrayToNewStuff(byte[] bytes)
{
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try
    {
        NewStuff stuff = (NewStuff)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(NewStuff));
    }
    finally
    {
        handle.Free();
    }
    return stuff;
}

Загальна версія:

T ByteArrayToStructure<T>(byte[] bytes) where T: struct 
{
    T stuff;
    GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try
    {
        stuff = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally
    {
        handle.Free();
    }
    return stuff;
}

Більш проста версія (потрібен unsafeперемикач):

unsafe T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
    fixed (byte* ptr = &bytes[0])
    {
        return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
    }
}

CS0411 Аргументи типу для методу 'ByteArrayToStructure <T> (байт [], int)' не можна вивести з використання. Спробуйте явно вказати аргументи типу. (Я додав до нього індекс байтового масиву).
SSpoke

Витік пам’яті за наявності винятків. Дивіться: stackoverflow.com/a/41836532/184528 для отримання більш безпечної версії.
cdiggins

2
Станом на 4.5.1, існує загальна версія PtrToStructure, тому другий рядок у загальній версії, вище, може стати: var stuff = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());

10

Ось виняток безпечної версії прийнятої відповіді :

public static T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
    var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    try {
        return (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
    }
    finally {
        handle.Free();
    }
}

3
@ Ben-Collins Прийняту відповідь було відредаговано після того, як я додав свою відповідь.
cdiggins

5

Слідкуйте за проблемами з упаковкою. У прикладі, який ви дали, усі поля мають очевидні зсуви, оскільки все знаходиться на 4 байтових межах, але це не завжди так. Visual C ++ пакується на 8-байтових межах за замовчуванням.


1
"Visual C ++ пакується на 8 байтових межах за замовчуванням." Це вирішило моє питання, велике спасибі!
Chris L

4
object ByteArrayToStructure(byte[] bytearray, object structureObj, int position)
{
    int length = Marshal.SizeOf(structureObj);
    IntPtr ptr = Marshal.AllocHGlobal(length);
    Marshal.Copy(bytearray, 0, ptr, length);
    structureObj = Marshal.PtrToStructure(Marshal.UnsafeAddrOfPinnedArrayElement(bytearray, position), structureObj.GetType());
    Marshal.FreeHGlobal(ptr);
    return structureObj;
}   

Майте це


0

Якщо у вас є байт [], ви повинні мати можливість використовувати клас BinaryReader і встановлювати значення на NewStuff, використовуючи доступні методи ReadX.

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