Lettura di una struttura dati C / C ++ in C # da una matrice di byte


86

Quale sarebbe il modo migliore per riempire una struttura C # da una matrice byte [] dove i dati provenivano da una struttura C / C ++? La struttura C sarebbe simile a questa (la mia C è molto arrugginita):

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

E riempirebbe qualcosa del genere:

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

Qual è il modo migliore per copiare OldStuffa NewStuff, se OldStuffè stata approvata come byte [] array?

Attualmente sto facendo qualcosa di simile a quanto segue, ma sembra un po 'goffo.

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

C'è un modo migliore per farlo?


L'utilizzo della BinaryReaderclasse offrirebbe miglioramenti in termini di prestazioni rispetto al blocco della memoria e all'utilizzo Marshal.PtrStructure?


1
Cordiali saluti, se il tuo programma viene eseguito su varie macchine potresti dover gestire il little vs big endian.
KPexEA

1
Come puoi gestirlo a livello della struttura, cioè senza dover invertire individualmente i byte per ogni valore nella struttura?
Pat

Risposte:


114

Da quello che posso vedere in quel contesto, non è necessario copiare SomeByteArrayin un buffer. Devi semplicemente ottenere la maniglia SomeByteArray, bloccarla, copiare i IntPtrdati utilizzando PtrToStructuree quindi rilasciare. Non c'è bisogno di una copia.

Sarebbe:

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

Versione generica:

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

Versione più semplice (richiede unsafeswitch):

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

CS0411 Gli argomenti del tipo per il metodo "ByteArrayToStructure <T> (byte [], int)" non possono essere dedotti dall'utilizzo. Prova a specificare esplicitamente gli argomenti del tipo. (Ho aggiunto int index dell'array di byte) ad esso.
S ha parlato il

Perderà memoria in presenza di eccezioni. Vedi: stackoverflow.com/a/41836532/184528 per una versione più sicura.
cdiggins

2
A partire dalla 4.5.1, esiste una versione generica di PtrToStructure, quindi la seconda riga nella versione generica, sopra, può diventare: var stuff = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());

10

Ecco una versione sicura rispetto alle eccezioni della risposta accettata :

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 La risposta accettata è stata modificata dopo aver aggiunto la mia risposta.
cdiggins

5

Fai attenzione ai problemi di imballaggio. Nell'esempio che hai fornito tutti i campi sono agli offset evidenti perché tutto è su limiti di 4 byte ma non sarà sempre così. Visual C ++ racchiude i limiti di 8 byte per impostazione predefinita.


1
"Visual C ++ racchiude i limiti di 8 byte per impostazione predefinita." Questo ha risolto il mio problema, grazie mille!
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;
}   

Prendi questo


0

Se hai un byte [] dovresti essere in grado di usare la classe BinaryReader e impostare i valori su NewStuff usando i metodi ReadX disponibili.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.