C# FAQ

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

FAQ

5 komentarzy

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?

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

Ajj, mea culpa. Dzięki

sczytać jest dobrze, nie zczytać. sprawdź w słowniku :)

"sczytać" => "zczytać" ;)