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ż:

5 komentarzy

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

  private static byte[] RawSerialize(object Anything)
    {
        int RawSize = Marshal.SizeOf(Anything);
        IntPtr Buffer = Marshal.AllocHGlobal(RawSize);
        Marshal.StructureToPtr(Anything, Buffer, false);
        byte[] RawDatas = new byte[RawSize];
        Marshal.Copy(Buffer, RawDatas, 0, RawSize);
        Marshal.FreeHGlobal(Buffer);
        return RawDatas;
    }

    private static object RawDeserialize(byte[] RawDatas, Type Anytype)
    {
        int RawSize = Marshal.SizeOf(Anytype);
        if (RawSize > RawDatas.Length)
            return null;
        IntPtr Buffer = Marshal.AllocHGlobal(RawSize);
        Marshal.Copy(RawDatas, 0, Buffer, RawSize);
        object RetObj = Marshal.PtrToStructure(Buffer, Anytype);
        Marshal.FreeHGlobal(Buffer);
        return RetObj;
    }

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