¿Cómo convertir una estructura en una matriz de bytes en C #?


83

¿Cómo convierto una estructura en una matriz de bytes en C #?

He definido una estructura como esta:

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

En mi método principal, creo una instancia y le asigno valores:

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

Ahora quiero enviar este paquete por socket. Para eso, necesito convertir la estructura en una matriz de bytes. ¿Cómo puedo hacerlo?

Mi código completo es el siguiente.

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

¿Qué sería un fragmento de código?


Una corrección en la última línea MyPing.Send (Toma matriz de bytes como parámetro); Es Send not SendTo ......
Swapnil Gupta

Hola Petar, no te entendí ...
Swapnil Gupta

3
Puede ser bueno aceptar algunas respuestas a sus preguntas anteriores.
jnoss

1
Sospecho que ayudaría ser un poco más específico sobre el resultado que espera; hay muchas formas de convertir eso en un byte [] ... Probablemente podamos hacer algunas suposiciones sobre la mayor parte, que desea las representaciones de tamaño fijo de orden de campo de orden de red de byte de los campos, pero ¿qué pasa con ¿la cuerda?
Marc Gravell

Tenga cuidado con Grand Endian y Little endian y alrededor de 32 Bits / 64 bits si selecciona la opción Marshall.
x77

Respuestas:


126

Esto es bastante fácil, usando la clasificación.

Parte superior del archivo

using System.Runtime.InteropServices

Función

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

Y para convertirlo de nuevo:

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

En su estructura, deberá poner esto antes de una cadena

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string Buffer;

Y asegúrese de que SizeConst sea tan grande como su cuerda más grande posible.

Y probablemente debería leer esto: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx


Gracias Vincet. GetBytes () debe llamarse después de enviar el byte [] ?? y el método frombytes () está enviando los bytes? ¿Soy un pequeño amigo confundido?
Swapnil Gupta

1
GetBytes convierte su estructura en una matriz. FromBytes convierte de Bytes a su estructura. Esto es evidente en las firmas de funciones.
Vincent McNabb

1
@Swapnil Esa es otra pregunta, que debe hacer por separado. Debería considerar completar un par de tutoriales de CE sobre sockets. Solo busca en Google.
Vincent McNabb

3
En su método fromBytes, no es necesario asignar el CIFSPacket dos veces. Marshal.SizeOf tomará felizmente un Type como parámetro y Marshal.PtrToStructure asigna un nuevo objeto administrado.
Jack Ukleja

1
Tenga en cuenta que en algunas circunstancias la función «StructureToPtr» arroja una excepción. Esto podría solucionarse pasando «falso» en lugar de «verdadero» a Marshal.StructureToPtr(str, ptr, false);. Pero debo mencionar que estoy usando las funciones envueltas en un genérico, aunque…
Hi-Angel

30

Si realmente desea que sea RÁPIDO en Windows, puede hacerlo utilizando un código inseguro con CopyMemory. CopyMemory es aproximadamente 5 veces más rápido (p. Ej., 800 MB de datos tardan 3 segundos en copiarse mediante clasificación, mientras que sólo se tarda 0,6 segundos en copiar mediante CopyMemory). Este método lo limita a usar solo datos que están realmente almacenados en la estructura del blob, por ejemplo, números o matrices de bytes de longitud fija.

    [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
Como un aviso para aquellos que están leyendo esta respuesta ... Esto no es compatible con plataformas cruzadas (solo usa Windows kernel32.dll). Pero, de nuevo, fue escrito en 2014. :)
Raid

24

Eche un vistazo a estos métodos:

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

¡Esta es una copia descarada de otro hilo que encontré al buscar en Google!

Actualización : para obtener más detalles, consulte la fuente


He convertido la estructura a una matriz de bytes usando Marshalling ahora, ¿cómo puedo verificar si obtengo la respuesta del socket? ¿Cómo comprobar eso?
Swapnil Gupta

@Alastair, ¡¡me perdí eso !! Gracias por señalarlo .. He actualizado mi respuesta.
Abdel Raoof

2
Esta opción depende de la plataforma: tenga cuidado con Grand Endian y Little endian y alrededor de 32 Bits / 64 bits.
x77

@Abdel, y el -1 se ha ido :)
Alastair Pitts

¿Tendría sentido realizar el Alloc, envolver el bit del medio en un intento y luego poner Free dentro de un finalmente? Parece poco probable que las cosas fallen, pero si lo hacen, ¿alguna vez se libera la memoria?
Casey

17

Variante del código de Vicent con una asignación de memoria menos:

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

Utilizo GCHandlepara "fijar" la memoria y luego uso directamente su dirección con h.AddrOfPinnedObject().


En caso de eliminarlo, de lo where T : structcontrario, se quejará de que Testá aprobado non-nullable type.
codenamezero

GCHandle.Allocfallará si la estructura tiene datos no blittables, por ejemplo, una matriz
joe

@joe Tienes razón. El código fue escrito para la estructura dada, que contenía solo tipos blittables y string.
xanatos

5

Como la respuesta principal es usar el tipo CIFSPacket, que no está (o ya no está) disponible en C #, escribí los métodos correctos:

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

Probados, funcionan.


3

Sé que es muy tarde, pero con C # 7.3 puede hacer esto para estructuras no administradas o cualquier otra cosa que no esté administrada (int, bool, etc.):

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

Entonces usa así:

struct MyStruct {
        public int Value1;
        public int Value2;
        //.. blah blah blah
    }

    byte[] bytes = ConvertToBytes(new MyStruct());

2

Puede usar Marshal (StructureToPtr, ptrToStructure) y Marshal.copy, pero esto depende de la plataforma.


La serialización incluye funciones para la serialización personalizada.

public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) 

SerializationInfo incluye funciones para serializar cada miembro.


BinaryWriter y BinaryReader también contienen métodos para Guardar / Cargar en Byte Array (Stream).

Tenga en cuenta que puede crear un MemoryStream desde un Byte Array o un Byte Array desde un MemoryStream.

Puede crear un método Guardar y un método Nuevo en su estructura:

   Save(Bw as BinaryWriter)
   New (Br as BinaryReader)

Luego, selecciona miembros para Guardar / Cargar en Stream -> Byte Array.


1

Esto se puede hacer de manera muy sencilla.

Defina su estructura explícitamente con [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;
}

Este código solo se puede escribir en un contexto inseguro. Tienes que liberar addrcuando termines.

Marshal.FreeHGlobal(addr);

Al realizar operaciones ordenadas explícitas en una colección de tamaño fijo, probablemente debería usar una matriz y un bucle for. La matriz porque tiene un tamaño fijo y el bucle for porque no se garantiza que foreach esté en el orden que espera, a menos que conozca la implementación subyacente de su tipo de lista y su enumerador, y que nunca cambiará. Se podría definir que el enumerador comience desde el final y retroceda, por ejemplo.

1

Se me ocurrió un enfoque diferente que podría convertir cualquiera struct sin la molestia de fijar la longitud, sin embargo, la matriz de bytes resultante tendría un poco más de sobrecarga.

He aquí una muestra struct:

[StructLayout(LayoutKind.Sequential)]
public class HelloWorld
{
    public MyEnum enumvalue;
    public string reqtimestamp;
    public string resptimestamp;
    public string message;
    public byte[] rawresp;
}

Como puede ver, todas esas estructuras requerirían agregar los atributos de longitud fija. Lo que a menudo podría terminar ocupando más espacio del requerido. Tenga en cuenta que LayoutKind.Sequentiales obligatorio, ya que queremos que la reflexión siempre nos dé el mismo orden al tirar de FieldInfo. Mi inspiración es de TLVTipo-Longitud-Valor. Echemos un vistazo al código:

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

La función anterior simplemente usa BinaryFormatterpara serializar el tamaño desconocido sin procesar object, y simplemente hago un seguimiento del tamaño también y lo guardo dentro de la salida MemoryStreamtambién.

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

Cuando queremos convertirlo de nuevo a su original struct, simplemente leemos la longitud y lo volcamos directamente de nuevo en el BinaryFormatterque a su vez lo volcamos de nuevo en el archivo struct.

Estas 2 funciones son genéricas y deberían funcionar con cualquiera struct, probé el código anterior en mi C#proyecto donde tengo un servidor y un cliente, me conecto y me comunico a través NamedPipeStreamy reenvío mi structmatriz de bytes de uno a otro y lo convertí .

Creo que mi enfoque podría ser mejor, ya que no fija la longitud en structsí mismo y la única sobrecarga es solo intpara cada campo que tiene en su estructura. También hay una pequeña sobrecarga dentro de la matriz de bytes generada por BinaryFormatter, pero aparte de eso, no es mucho.


6
Generalmente, cuando las personas intentan lidiar con este tipo de cosas, también les preocupa el rendimiento de la serialización. En teoría, cualquier matriz de estructuras se puede reinterpretar como una matriz de bytes sin involucrar una serialización y copia costosas.
Tanveer Badar


0

Parece una estructura predefinida (nivel C) para alguna biblioteca externa. Marshal es tu amigo. Cheque:

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

para empezar, cómo lidiar con esto. Tenga en cuenta que puede, con atributos, definir cosas como el diseño de bytes y el manejo de cadenas. MUY buen enfoque, de hecho.

Ni BinaryFormatter ni MemoryStream están hechos para eso.


0

@Abdel Olakara respuesta donese no funciona en .net 3.5, debe modificarse de la siguiente manera:

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

Esto debería funcionar rápidamente, ¿verdad?


La versión de GCHandle es mucho mejor.
Петър Петров

0

Este ejemplo aquí solo es aplicable a tipos puros blittable, por ejemplo, tipos que se pueden memorizar directamente en C.

Ejemplo: estructura conocida de 64 bits

[StructLayout(LayoutKind.Sequential)]  
public struct Voxel
{
    public ushort m_id;
    public byte m_red, m_green, m_blue, m_alpha, m_matid, m_custom;
}

Definida exactamente así, la estructura se empaquetará automáticamente como de 64 bits.

Ahora podemos crear volumen de vóxeles:

Voxel[,,] voxels = new Voxel[16,16,16];

Y guárdelos todos en una matriz de bytes:

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.

Sin embargo, dado que el OP quiere saber cómo convertir la estructura en sí, nuestra estructura Voxel puede tener el siguiente método 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();
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.