Marshaling - jak zamienić strukturę na tablicę
TeWuX
Marshaling można by powiedzieć, że jest to pewna, niezarządzana odmiana serializacji. Z tą różnicą, że serializując obiekt przechowujemy również jego strukturę, natomiast przy marshalingu wyłącznie same dane. W tym artykule opiszemy jak zamienić strukturę na tablicę byte'ów i vice-versa.
Niedawno spotkałem się z problemem, jak komunikować się z pewnym serwerem przez Sockety. Problem stanowiła zamiana struktury (np. headera) na tablice byte'ów oraz operacja odwrotna.
Z pomocą przyszła klasa System.Runtime.InteropServices
. Marshal , która zawiera szereg metod statycznych ułatwiających zadanie.
Stwórzmy najpierw klasę abstrakcyjną, która będzie zawierała metody do konwersji z i na tablicę.
// atrybut zapewniający, że pola będą w takim porządku jak je zadeklarowano
[StructLayout(LayoutKind.Sequential)]
abstract class BinaryStruct
{
public virtual byte[] ToArray()
{
// pobieramy wielkość struktury w byte'ach
int size = Marshal.SizeOf(this);
// tworzymy wskaźnik i przydzielamy mu miejsce
IntPtr ptr = Marshal.AllocHGlobal(size);
// kopiujemy strukturę na wskaźnik
Marshal.StructureToPtr(this, ptr, false);
// deklarujemy tablice i kopiujemy do niej to co mamy pod wskaźnikiem
byte[] array = new byte[size];
Marshal.Copy(ptr, array, 0, size);
// zwalniamy pamięć
Marshal.FreeHGlobal(ptr);
return array;
}
public virtual void FromArray(byte[] val)
{
// analogicznie jak ToArray()
int size = Marshal.SizeOf(this);
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(val, 0, ptr, size);
Marshal.PtrToStructure(ptr, this);
Marshal.FreeHGlobal(ptr);
}
}
Następnie deklarujemy klasy, które będziemy marshalingować, tak aby dziedziczyły po BinaryStruct
:
[StructLayout(LayoutKind.Sequential)]
class Header : BinaryStruct
{
public int Type;
public int Length;
}
[StructLayout(LayoutKind.Sequential)]
class Message : BinaryStruct
{
public int Tag;
// atrybut oznaczający, że string zostanie "wzięty" po wartości, a nie referencji
// SizeConst=64 ustawia jego stałą długość na 64 znaki
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)]
public string Content;
}
Następnie w swoim projekcie możemy już swobodnie sczytać obiekt do tablicy i np. wysłać go przez socket.
Header h = new Header();
h.stan = 256;
h.length = 370;
TcpClient client = new TcpClient("mojserwer", 1234);
NetworkStream stream = client.GetStream();
stream.Write(h.ToArray(), 0, Marshal.SizeOf(h));
Message m = new Message();
int size = Marshal.SizeOf(m);
byte[] buff = new byte[size];
stream.Read(buff, 0, size);
m.FromArray(buff);
Zobacz też:
Deti, tak będzie trybić, ale trzeba dać odpowiedni atrybut dla tego pola, żeby nie brał referencji, bądź wskaźnika tylko cały string, ale tylko o stałej długości.
Zaraz edytuje artykuł i dam taki przykład ;)
Może i to komuś się przyda :)
by the way: TeWux: a jeśli w strukturze byłoby pole typu string? Będzie trybić jak trzeba?
Ajj, mea culpa. Dzięki
sczytać jest dobrze, nie zczytać. sprawdź w słowniku :)
"sczytać" => "zczytać" ;)