Leer una estructura de datos C / C ++ en C # desde una matriz de bytes

85

¿Cuál sería la mejor manera de llenar una estructura C # desde una matriz de bytes [] donde los datos provienen de una estructura C / C ++? La estructura de C se vería así (mi C está muy oxidada):

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

Y llenaría algo como esto:

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

¿Cuál es la mejor manera de copiar OldStuffa NewStuff, si OldStuffse pasa como byte [matriz]?

Actualmente estoy haciendo algo como lo siguiente, pero se siente un poco torpe.

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

¿Existe una mejor manera de lograr esto?


¿Usar la BinaryReaderclase ofrecería alguna mejora de rendimiento en comparación con anclar la memoria y usarla Marshal.PtrStructure?

Chris Miller
fuente
1
Para su información, si su programa se ejecuta en varias máquinas, es posible que deba manejar little vs big endian.
KPexEA
1
¿Cómo puede manejar eso en el nivel de la estructura, es decir, sin tener que invertir individualmente los bytes para cada valor en la estructura?
Pat

Respuestas:

114

Por lo que puedo ver en ese contexto, no es necesario copiar SomeByteArrayen un búfer. Simplemente necesita obtener el identificador de SomeByteArray, fijarlo, copiar los IntPtrdatos usando PtrToStructurey luego liberar. No necesita copia.

Eso sería:

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

Versión genérica:

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

Versión más simple (requiere unsafeinterruptor):

unsafe T ByteArrayToStructure<T>(byte[] bytes) where T : struct
{
    fixed (byte* ptr = &bytes[0])
    {
        return (T)Marshal.PtrToStructure((IntPtr)ptr, typeof(T));
    }
}
Moneda
fuente
CS0411 Los argumentos de tipo para el método 'ByteArrayToStructure <T> (byte [], int)' no se pueden inferir del uso. Intente especificar los argumentos de tipo explícitamente. (Le agregué int índice de matriz de bytes).
Habló el
Perderá memoria en presencia de excepciones. Consulte: stackoverflow.com/a/41836532/184528 para obtener una versión más segura.
cdiggins
2
A partir de 4.5.1, hay una versión genérica de PtrToStructure, por lo que la segunda línea de la versión genérica, arriba, puede convertirse en: var stuff = Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
10

Aquí hay una versión segura de excepción de la respuesta aceptada :

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();
    }
}
cdiggins
fuente
3
@ Ben-Collins La respuesta aceptada se editó después de que agregué mi respuesta.
cdiggins
5

Tenga cuidado con los problemas de embalaje. En el ejemplo que proporcionó, todos los campos están en los desplazamientos obvios porque todo está en límites de 4 bytes, pero este no siempre será el caso. Visual C ++ se empaqueta en límites de 8 bytes de forma predeterminada.

Tim anillo
fuente
1
"Visual C ++ se empaqueta en límites de 8 bytes de forma predeterminada". Esto resolvió mi problema, ¡muchas gracias!
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;
}   

Tengo esto

Dushyant
fuente
0

Si tiene un byte [], debería poder usar la clase BinaryReader y establecer valores en NewStuff usando los métodos ReadX disponibles.

Mufaka
fuente