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?
Odpowiedzi:
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
źródło
Marshal.StructureToPtr(str, ptr, false);
. Ale muszę wspomnieć, że używam funkcji zapakowanych do ogólnej, chociaż…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; }
źródło
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
źródło
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
GCHandle
do „przypinania” pamięci, a następnie używam bezpośrednio jej adresuh.AddrOfPinnedObject()
.źródło
where T : struct
przeciwnym razie skarga naT
bycie zdanym nie jestnon-nullable type
.GCHandle.Alloc
nie powiedzie się, jeśli struktura ma dane, których nie można przesłać, np. tablicęstring
.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ą.
źródło
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());
źródło
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.
źródło
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ć,
addr
kiedy skończysz.źródło
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.Sequential
jest wymagane, ponieważ chcemy, aby odbicie zawsze dawało nam tę samą kolejność podczas przyciąganiaFieldInfo
. Inspiruje mnieTLV
typ-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
BinaryFormatter
do serializacji nieznanego rozmiaru w stanie surowymobject
, a ja po prostu śledzę również rozmiar i przechowuję go również w danych wyjściowychMemoryStream
.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,BinaryFormatter
który z kolei zrzuca go z powrotem do plikustruct
.Te 2 funkcje są ogólne i powinny działać z dowolną
struct
, przetestowałem powyższy kod w moimC#
projekcie, w którym mam serwer i klienta, połączono i komunikuję się przezNamedPipeStream
i przekazuję mojąstruct
tablicę 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
struct
samego siebie, a jedyne obciążenie to tylkoint
dla każdego pola, które masz w swojej strukturze. Istnieje również trochę narzutu wewnątrz tablicy bajtów generowanej przezBinaryFormatter
, ale poza tym nie jest dużo.źródło
Przyjrzałbym się klasom BinaryReader i BinaryWriter. Niedawno musiałem serializować dane do tablicy bajtów (iz powrotem) i znalazłem te klasy dopiero po tym, jak sam je przepisałem.
http://msdn.microsoft.com/en-us/library/system.io.binarywriter.aspx
Na tej stronie jest też dobry przykład.
źródło
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.
źródło
@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); }
źródło
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?
źródło
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