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

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

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

Ajj, mea culpa. Dzięki

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?