Een C/C++ datastructuur lezen in C# uit een bytearray

Wat zou de beste manier zijn om een ​​C#-struct uit een byte[]-array te vullen waar de gegevens uit een C/C++-struct kwamen? De C-structuur ziet er ongeveer zo uit (mijn C is erg roestig):

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

En zou zoiets als dit vullen:

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

Wat is de beste manier om OldStuffnaar NewStuffte kopiëren, als OldStuffis doorgegeven als byte[]-array?

Ik doe momenteel zoiets als het volgende, maar het voelt een beetje onhandig aan.

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

Is er een betere manier om dit te bereiken?


Zou het gebruik van de klasse BinaryReaderenige prestatiewinst opleveren ten opzichte van het vastzetten van het geheugen en het gebruik van Marshal.PtrStructure?


Antwoord 1, autoriteit 100%

Voor zover ik in die context kan zien, hoef je SomeByteArrayniet naar een buffer te kopiëren. Je hoeft alleen maar het handvat van SomeByteArrayte halen, het vast te pinnen, de IntPtr-gegevens te kopiëren met behulp van PtrToStructureen vervolgens los te laten. Geen kopie nodig.

Dat zou zijn:

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

Algemene versie:

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

Eenvoudigere versie (vereist unsafeschakelaar):

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

Antwoord 2, autoriteit 10%

Hier is een uitzonderingsveilige versie van het geaccepteerde antwoord:

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

Antwoord 3, autoriteit 6%

Kijk uit voor verpakkingsproblemen. In het voorbeeld gaf je alle velden op de voor de hand liggende offsets omdat alles op 4 byte grenzen is, maar dit zal niet altijd het geval zijn. Visual C++ Packs op 8 byte-grenzen Standaard.

Other episodes