Jak przekonwertować strukturę na tablicę bajtów w C #?

83

Jak przekonwertować strukturę na tablicę bajtów w języku C #?

Zdefiniowałem taką strukturę:

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

W mojej głównej metodzie tworzę jej instancję i przypisuję do niej wartości:

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

Teraz chcę wysłać ten pakiet przez gniazdo. W tym celu muszę przekonwertować strukturę na tablicę bajtów. Jak mogę to zrobić?

Mój pełny kod jest następujący.

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

Czym byłby fragment kodu?

Swapnil Gupta
źródło
Jedna poprawka w ostatniej linii MyPing.Send (przyjmuje tablicę bajtów jako parametr); To jest Send not
SendTo
Cześć Petar, nie dostałem cię ...
Swapnil Gupta
3
Dobrze byłoby przyjąć kilka odpowiedzi na swoje poprzednie pytania.
jnoss
1
Podejrzewam, że przydałoby się trochę bardziej szczegółowe określenie oczekiwanego wyniku; istnieje wiele sposobów przekształcenia tego w bajt [] ... Prawdopodobnie możemy przyjąć pewne założenia dotyczące większości z nich, że chcesz mieć reprezentacje pól o stałym rozmiarze w kolejności bajtów w sieci - ale co z sznurek?
Marc Gravell
Uważaj na Grand Endian i Little endian oraz o 32 bity / 64 bity, jeśli wybierzesz opcję Marshall.
x77,

Odpowiedzi:

126

Jest to dość łatwe przy użyciu krosowania.

Początek pliku

using System.Runtime.InteropServices

Funkcjonować

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

I aby przekonwertować go z powrotem:

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

W swojej strukturze będziesz musiał umieścić to przed napisem

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

I upewnij się, że SizeConst jest tak duży, jak twój największy możliwy ciąg.

I prawdopodobnie powinieneś przeczytać to: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx

Vincent McNabb
źródło
Dzięki Vincet. GetBytes () należy wywołać po wysłaniu bajtu [] ?? a metoda frombytes () wysyła bajty? Jestem małym zdezorientowanym kumplem?
Swapnil Gupta
1
GetBytes konwertuje z Twojej struktury na tablicę. FromBytes konwertuje z bajtów z powrotem do Twojej struktury. Wynika to wyraźnie z sygnatur funkcji.
Vincent McNabb,
1
@Swapnil To kolejne pytanie, które powinieneś zadać osobno. Powinieneś rozważyć ukończenie kilku samouczków CE na temat gniazd. Po prostu wyszukaj w Google.
Vincent McNabb,
3
W metodzie fromBytes nie ma potrzeby dwukrotnego przydzielania pakietu CIFSPacket. Marshal.SizeOf szczęśliwie przyjmie Type jako parametr, a Marshal.PtrToStructure przydzieli nowy obiekt zarządzany.
Jack Ukleja
1
Zauważ, że w pewnych okolicznościach funkcja «StructureToPtr» zgłasza wyjątek. Można to naprawić, przekazując „fałsz” zamiast „prawda” do Marshal.StructureToPtr(str, ptr, false);. Ale muszę wspomnieć, że używam funkcji zapakowanych do ogólnej, chociaż…
Hi-Angel,
30

Jeśli naprawdę chcesz, aby działało SZYBKO w systemie Windows, możesz to zrobić, używając niebezpiecznego kodu z CopyMemory. CopyMemory jest około 5x szybsze (np. Kopiowanie 800MB danych zajmuje 3s poprzez krosowanie, podczas gdy kopiowanie przez CopyMemory zajmuje tylko 0,6s). Ta metoda nie ogranicza Cię do używania tylko danych, które są faktycznie przechowywane w samym obiekcie blob struktury, np. Liczb lub tablic bajtów o stałej długości.

    [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;
    }
Paweł
źródło
4
Jako ostrzeżenie dla tych, którzy czytają tę odpowiedź. To nie jest przyjazne dla wielu platform (używa tylko kernel32.dll systemu Windows). Ale z drugiej strony, został napisany w 2014 roku. :)
Raid
2
Dodatkowo wymaga, aby struktura była sekwencyjna.
Tomer W
25

Spójrz na te metody:

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

To bezwstydna kopia innego wątku, który znalazłem w Google!

Aktualizacja : aby uzyskać więcej informacji, sprawdź źródło

Abdel Raoof
źródło
Mam strukturę convertd do tablicy bajtów przy użyciu Marshalling teraz, jak mogę sprawdzić, czy otrzymuję odpowiedź z gniazda? Jak to sprawdzić?
Swapnil Gupta
@Alastair, przegapiłem to !! Dzięki za wskazanie tego… Zaktualizowałem moją odpowiedź.
Abdel Raoof,
2
Ta opcja jest zależna od platformy - zadbaj o Grand Endian i Little endian oraz o 32 bity / 64 bity.
x77,
@Abdel, a -1 zniknęło :)
Alastair Pitts
Czy miałoby sens wykonanie Alloc, zawinięcie środkowego kawałka w próbie, a następnie umieszczenie Free w końcu? Wydaje się mało prawdopodobne, że coś się nie uda, ale jeśli tak się stanie, czy pamięć kiedykolwiek zostanie uwolniona?
Casey
18

Wariant kodu Vicent z jedną alokacją pamięci mniej:

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

Używam GCHandledo „przypinania” pamięci, a następnie używam bezpośrednio jej adresu h.AddrOfPinnedObject().

xanatos
źródło
Powinien usunąć, w where T : structprzeciwnym razie skarga na Tbycie zdanym nie jest non-nullable type.
codenamezero
GCHandle.Allocnie powiedzie się, jeśli struktura ma dane, których nie można przesłać, np. tablicę
joe
@joe Masz rację. Kod został napisany dla danej struktury, która zawierała tylko typy kopiowalne i string.
xanatos
5

Ponieważ główną odpowiedzią jest użycie typu CIFSPacket, który nie jest (lub już nie jest) dostępny w C #, napisałem poprawne metody:

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

Przetestowane, działają.

pbies
źródło
4

Wiem, że to naprawdę późno, ale w C # 7.3 możesz to zrobić dla niezarządzanych struktur lub czegokolwiek innego, co niezmienione (int, bool itp ...):

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

Następnie użyj w ten sposób:

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

    byte[] bytes = ConvertToBytes(new MyStruct());
Varscott128
źródło
2

Możesz użyć Marshal (StructureToPtr, ptrToStructure) i Marshal.copy, ale jest to zależne od plataform.


Serializacja obejmuje funkcje do niestandardowej serializacji.

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

SerializationInfo obejmuje funkcje do serializacji każdego elementu członkowskiego.


BinaryWriter i BinaryReader zawierają również metody Save / Load to Byte Array (Stream).

Zauważ, że możesz utworzyć MemoryStream z tablicy Byte Array lub Byte Array z MemoryStream.

Możesz utworzyć metodę Save i metodę New w swojej strukturze:

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

Następnie wybierasz członków do zapisania / załadowania do strumienia -> Tablica bajtów.

x77
źródło
1

Można to zrobić bardzo prosto.

Zdefiniuj wyraźnie swoją strukturę za pomocą [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;
}

Ten kod można napisać tylko w niebezpiecznym kontekście. Musisz się uwolnić, addrkiedy skończysz.

Marshal.FreeHGlobal(addr);
DianeS
źródło
Podczas wykonywania jawnych operacji uporządkowanych na kolekcji o stałym rozmiarze, prawdopodobnie powinieneś użyć tablicy i pętli for. Tablica, ponieważ ma stały rozmiar, i pętla for, ponieważ foreach nie ma gwarancji, że będzie w oczekiwanej kolejności, chyba że znasz podstawową implementację typu listy i jej modułu wyliczającego i że nigdy się nie zmieni. Można na przykład zdefiniować moduł wyliczający, aby zaczynał od końca i cofał się.
1

Wymyśliłem inne podejście, które może konwertować dowolne struct bez kłopotów z ustalaniem długości, jednak wynikowa tablica bajtów miałaby nieco więcej narzutu.

Oto próbka struct:

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

Jak widać, wszystkie te struktury wymagałyby dodania atrybutów o stałej długości. Co często wymagało więcej miejsca niż to konieczne. Zwróć uwagę, że LayoutKind.Sequentialjest wymagane, ponieważ chcemy, aby odbicie zawsze dawało nam tę samą kolejność podczas przyciągania FieldInfo. Inspiruje mnie TLVtyp-długość-wartość. Spójrzmy na kod:

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

Powyższa funkcja po prostu używa the BinaryFormatterdo serializacji nieznanego rozmiaru w stanie surowym object, a ja po prostu śledzę również rozmiar i przechowuję go również w danych wyjściowych 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));
            }
        }
    }
}

Kiedy chcemy przekonwertować go z powrotem na oryginał struct, po prostu odczytujemy długość z powrotem i bezpośrednio wrzucamy z powrotem do pliku, BinaryFormatterktóry z kolei zrzuca go z powrotem do pliku struct.

Te 2 funkcje są ogólne i powinny działać z dowolną struct, przetestowałem powyższy kod w moim C#projekcie, w którym mam serwer i klienta, połączono i komunikuję się przez NamedPipeStreami przekazuję moją structtablicę bajtów as z jednej do drugiej i przekonwertowałem ją z powrotem .

Uważam, że moje podejście może być lepsze, ponieważ nie określa długości structsamego siebie, a jedyne obciążenie to tylko intdla każdego pola, które masz w swojej strukturze. Istnieje również trochę narzutu wewnątrz tablicy bajtów generowanej przez BinaryFormatter, ale poza tym nie jest dużo.

codenamezero
źródło
6
Generalnie, gdy ludzie próbują poradzić sobie z takimi rzeczami, martwią się również o wydajność serializacji. Teoretycznie każda tablica struktur może zostać ponownie zinterpretowana jako tablica bajtów bez angażowania kosztownej serializacji i kopiowania.
Tanveer Badar
0

Wygląda na wstępnie zdefiniowaną strukturę (poziom C) dla jakiejś zewnętrznej biblioteki. Marshal to twój przyjaciel. Czek:

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

na początek, jak sobie z tym poradzić. Zauważ, że możesz - za pomocą atrybutów - zdefiniować takie rzeczy, jak układ bajtów i obsługa łańcuchów. Właściwie to BARDZO fajne podejście.

Ani BinaryFormatter ani MemoryStream nie są do tego przeznaczone.

TomTom
źródło
0

@Abdel Olakara odpowiedź donese nie działa w .net 3.5, należy ją zmodyfikować jak poniżej:

    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);
    }
lsaturn
źródło
0
        Header header = new Header();
        Byte[] headerBytes = new Byte[Marshal.SizeOf(header)];
        Marshal.Copy((IntPtr)(&header), headerBytes, 0, headerBytes.Length);

To powinno szybko załatwić sprawę, prawda?

Ryan Brown
źródło
Wersja GCHandle jest znacznie lepsza.
Петър Петров
0

Ten przykład ma zastosowanie tylko do typów czysto kopiowalnych, np. Typów, które można zapisać bezpośrednio w C.

Przykład - dobrze znana struktura 64-bitowa

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

Zdefiniowana dokładnie w ten sposób struktura zostanie automatycznie spakowana jako 64-bitowa.

Teraz możemy stworzyć objętość wokseli:

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

I zapisz je wszystkie w tablicy bajtów:

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.

Jednak ponieważ OP chce wiedzieć, jak przekonwertować samą strukturę, nasza struktura Voxel może mieć następującą metodę 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();
Петър Петров
źródło