Wie konvertiere ich eine Struktur in ein Byte-Array in C #?


81

Wie konvertiere ich eine Struktur in ein Byte-Array in C #?

Ich habe eine Struktur wie diese definiert:

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

In meiner Hauptmethode erstelle ich eine Instanz davon und weise ihr Werte zu:

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

Jetzt möchte ich dieses Paket per Socket senden. Dafür muss ich die Struktur in ein Byte-Array konvertieren. Wie kann ich es tun?

Mein vollständiger Code lautet wie folgt.

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

Was wäre ein Code-Snippet?


Eine Korrektur in der letzten Zeile MyPing.Send (Es wird ein Byte-Array als Parameter verwendet); Es ist Send not SendTo ......
Swapnil Gupta

Hallo Petar, ich habe dich nicht verstanden ...
Swapnil Gupta

3
Es kann hilfreich sein, einige Antworten auf Ihre vorherigen Fragen zu akzeptieren.
jnoss

1
Ich vermute, es würde helfen, die erwartete Ausgabe etwas genauer zu beschreiben. Es gibt viele Möglichkeiten, dies in ein Byte umzuwandeln [] ... Wir können wahrscheinlich einige Annahmen darüber treffen, dass Sie die Darstellungen der Felder in Netzwerkreihenfolge mit fester Größe in Feldreihenfolge in Feldreihenfolge wünschen - aber was ist damit? die Saite?
Marc Gravell

Achten Sie auf Grand Endian und Little Endian sowie auf 32 Bit / 64 Bit, wenn Sie die Option Marshall auswählen.
x77

Antworten:


125

Dies ist mit Marshalling ziemlich einfach.

Anfang der Datei

using System.Runtime.InteropServices

Funktion

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

Und um es zurück zu konvertieren:

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

In Ihrer Struktur müssen Sie dies vor eine Zeichenfolge setzen

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

Und stellen Sie sicher, dass SizeConst so groß ist wie Ihre größtmögliche Zeichenfolge.

Und Sie sollten dies wahrscheinlich lesen: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx


Danke Vincet. GetBytes () sollte nach dem Senden des Bytes [] aufgerufen werden? und frombytes () Methode sendet die Bytes? Ich bin ein kleiner verwirrter Kumpel?
Swapnil Gupta

1
GetBytes konvertiert von Ihrer Struktur in ein Array. FromBytes konvertiert von den Bytes zurück in Ihre Struktur. Dies geht aus den Funktionssignaturen hervor.
Vincent McNabb

1
@ Swapnil Das ist eine andere Frage, die Sie separat stellen sollten. Sie sollten in Betracht ziehen, einige CE-Tutorials für Steckdosen zu absolvieren. Suchen Sie einfach bei Google.
Vincent McNabb

3
In Ihrer fromBytes-Methode muss das CIFSPacket nicht zweimal zugewiesen werden. Marshal.SizeOf nimmt gerne einen Typ als Parameter und Marshal.PtrToStructure weist ein neues verwaltetes Objekt zu.
Jack Ukleja

1
Beachten Sie, dass die Funktion «StructureToPtr» unter Umständen eine Ausnahme auslöst. Dies könnte behoben werden, indem «false» anstelle von «true» an übergeben wird Marshal.StructureToPtr(str, ptr, false);. Aber ich muss erwähnen, dass ich die Funktionen verwende, die in ein Generikum eingebunden sind…
Hi-Angel

30

Wenn Sie wirklich möchten, dass es unter Windows SCHNELL ist, können Sie dies mit unsicherem Code mit CopyMemory tun. CopyMemory ist ungefähr 5x schneller (z. B. 800 MB Daten benötigen 3 Sekunden zum Kopieren über Marshalling, während nur 0,6 Sekunden zum Kopieren über CopyMemory benötigt werden). Diese Methode beschränkt sich darauf, nur Daten zu verwenden, die tatsächlich im Struktur-Blob selbst gespeichert sind, z. B. Zahlen oder Byte-Arrays mit fester Länge.

    [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
Als Heads-Up für diejenigen, die diese Antwort lesen. Dies ist nicht plattformübergreifend (es wird nur Windows kernel32.dll verwendet). Aber andererseits wurde es im Jahr 2014 geschrieben. :)
Raid

24

Schauen Sie sich diese Methoden an:

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

Dies ist eine schamlose Kopie eines anderen Threads, den ich beim Googeln gefunden habe!

Update : Weitere Informationen finden Sie in der Quelle


Ich habe Converterd Structure to Byte Array mit Marshalling jetzt, wie kann ich überprüfen, ob ich die Antwort von Socket bekomme? Wie überprüfe ich das?
Swapnil Gupta

@Alastair, das habe ich verpasst !! Vielen Dank für den Hinweis. Ich habe meine Antwort aktualisiert.
Abdel Raoof

2
Diese Option ist plattformabhängig. Achten Sie auf Grand Endian und Little Endian sowie auf 32 Bit / 64 Bit.
x77

@Abdel, und die -1 ist weg :)
Alastair Pitts

Wäre es sinnvoll, das Alloc durchzuführen, das mittlere Bit in einen Versuch zu wickeln und dann Free in ein final zu setzen? Es ist unwahrscheinlich, dass Dinge scheitern, aber wenn doch, wird die Erinnerung jemals freigegeben?
Casey

17

Variante des Vicent-Codes mit einer Speicherzuordnung weniger:

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

Ich benutze GCHandle, um den Speicher zu "pinnen" und dann benutze ich direkt seine Adresse mit h.AddrOfPinnedObject().


Sollte entfernt werden, wird es where T : structsonst Tkeine Beschwerde über das Bestehen geben non-nullable type.
Codenamezero

GCHandle.Allocwird fehlschlagen, wenn die Struktur nicht blittable Daten hat, z. B. ein Array
Joe

@ Joe Du hast recht. Der Code wurde für die angegebene Struktur geschrieben, die nur blittable Typen und enthielt string.
Xanatos

5

Da die Hauptantwort die Verwendung des CIFSPacket-Typs ist, der in C # nicht (oder nicht mehr) verfügbar ist, habe ich die richtigen Methoden geschrieben:

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

Getestet funktionieren sie.


3

Ich weiß, dass dies sehr spät ist, aber mit C # 7.3 können Sie dies für nicht verwaltete Strukturen oder alles andere tun, was nicht geändert wurde (int, bool usw.):

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

Dann verwenden Sie wie folgt:

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

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

2

Sie können Marshal (StructureToPtr, ptrToStructure) und Marshal.copy verwenden, dies ist jedoch plataformabhängig.


Die Serialisierung umfasst Funktionen zur benutzerdefinierten Serialisierung.

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

SerializationInfo enthält Funktionen zum Serialisieren jedes Mitglieds.


BinaryWriter und BinaryReader enthalten auch Methoden zum Speichern / Laden in Byte-Array (Stream).

Beachten Sie, dass Sie einen MemoryStream aus einem Byte-Array oder ein Byte-Array aus einem MemoryStream erstellen können.

Sie können eine Methode Speichern und eine Methode Neu in Ihrer Struktur erstellen:

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

Anschließend wählen Sie Mitglieder zum Speichern / Laden zum Streamen -> Byte-Array aus.


1

Dies kann sehr einfach erfolgen.

Definieren Sie Ihre Struktur explizit mit [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;
}

Dieser Code kann nur in einem unsicheren Kontext geschrieben werden. Sie müssen befreien, addrwenn Sie damit fertig sind.

Marshal.FreeHGlobal(addr);

Wenn Sie explizit geordnete Operationen für eine Sammlung mit fester Größe ausführen, sollten Sie wahrscheinlich ein Array und eine for-Schleife verwenden. Das Array, weil es eine feste Größe hat, und die for-Schleife, weil foreach nicht garantiert in der erwarteten Reihenfolge ist, es sei denn, Sie kennen die zugrunde liegende Implementierung Ihres Listentyps und seines Enumerators und es wird sich nie ändern. Man könnte den Enumerator beispielsweise so definieren, dass er am Ende beginnt und rückwärts geht.

1

Ich habe mir einen anderen Ansatz ausgedacht, der jeden struct ohne den Aufwand der Fixierungslänge konvertieren könnte , jedoch würde das resultierende Byte-Array etwas mehr Overhead haben.

Hier ist ein Beispiel struct:

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

Wie Sie sehen können, müssten für alle diese Strukturen die Attribute mit fester Länge hinzugefügt werden. Was oft mehr Platz beanspruchte als nötig. Beachten Sie, dass dies LayoutKind.Sequentialerforderlich ist, da die Reflexion beim Ziehen immer die gleiche Reihenfolge ergibt FieldInfo. Meine Inspiration ist vom TLVTyp-Länge-Wert. Schauen wir uns den Code an:

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

Die obige Funktion verwendet einfach die, BinaryFormatterum die unbekannte Rohgröße zu serialisieren object, und ich verfolge einfach auch die Größe und speichere sie auch in der Ausgabe MemoryStream.

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

Wenn wir es wieder in sein Original konvertieren wollen, structlesen wir einfach die Länge zurück und werfen es direkt zurück in das, BinaryFormatterwas es wiederum zurück in das struct.

Diese beiden Funktionen sind generisch und sollten mit allen funktionieren. structIch habe den obigen Code in meinem C#Projekt getestet, in dem ich einen Server und einen Client habe, über die NamedPipeStreamich verbunden bin und kommuniziere, und ich leite mein structAs-Byte-Array von einem zum anderen weiter und konvertiere es zurück .

Ich glaube, mein Ansatz könnte besser sein, da er nicht die Länge an sich structselbst festlegt und der einzige Overhead nur intfür alle Felder gilt, die Sie in Ihrer Struktur haben. Es gibt auch ein kleines bisschen Overhead innerhalb des Byte-Arrays, das von generiert wird BinaryFormatter, aber ansonsten ist es nicht viel.


6
Wenn Leute versuchen, mit solchen Dingen umzugehen, sind sie im Allgemeinen auch besorgt über die Serialisierungsleistung. Theoretisch kann jedes Array von Strukturen als Byte-Array neu interpretiert werden, ohne dass teure Serialisierung und Kopieren erforderlich sind.
Tanveer Badar


0

Sieht aus wie eine vordefinierte Struktur (C-Ebene) für eine externe Bibliothek. Marschall ist dein Freund. Prüfen:

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

für den Anfang, wie man damit umgeht. Beachten Sie, dass Sie mit Attributen beispielsweise das Byte-Layout und die Behandlung von Zeichenfolgen definieren können. Eigentlich ein sehr netter Ansatz.

Weder BinaryFormatter noch MemoryStream werden dafür ausgeführt.


0

@Abdel Olakara Antwort donese funktioniert nicht in .net 3.5, sollte wie folgt geändert werden:

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

Das sollte schnell gehen, oder?


Die GCHandle-Version ist weitaus besser.
27етър Петров

0

Dieses Beispiel hier gilt nur für reine blittable Typen, z. B. Typen, die direkt in C gespeichert werden können.

Beispiel - bekannte 64-Bit-Struktur

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

Genau so definiert wird die Struktur automatisch als 64-Bit gepackt.

Jetzt können wir ein Volumen von Voxeln erstellen:

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

Und speichern Sie sie alle in einem Byte-Array:

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.

Da das OP jedoch wissen möchte, wie die Struktur selbst konvertiert wird, kann unsere Voxel-Struktur über die folgende Methode verfügen 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();
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.