Programowanie w języku C# » FAQ

Marshaling - jak zamienić strukturę na tablicę

  • 2007-11-13 22:18
  • 5 komentarzy
  • 1517 odsłon
  • Oceń ten tekst jako pierwszy
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

TeWuX 2007-11-12 19:09

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

Deti 2007-11-12 17:13

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?

Coldpeer 2007-11-12 15:15

Ajj, mea culpa. Dzięki

TeWuX 2007-11-12 14:44

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

Coldpeer 2007-11-12 14:40

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