C# пакет 1 StructLayout работа в мрежа

Опитвам се да изпратя буфер от сървър към клиент, който съм направил сам. Работи със сокети на TCP.

Имам структура, която трябва да изпратя:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct loginStruct
{

    public string userName;
    public string password;

    public loginStruct(string userName, string password)
    {
        this.userName = userName;
        this.password = password;
    }
}

И получих тези функции за преобразуване от байтов масив в структура и от структура в байтов масив:

    public static byte[] StructToByteArray(object obj)
    {
        int len = Marshal.SizeOf(obj);
        byte[] arr = new byte[len];

        IntPtr ptr = Marshal.AllocHGlobal(len);
        Marshal.StructureToPtr(obj, ptr, false);
        Marshal.Copy(ptr, arr, 0, len);

        Marshal.FreeHGlobal(ptr);
        return arr;

    }
    public static void ByteArrayToStruct(byte[] buffer, ref object obj)
    {
        int len = Marshal.SizeOf(obj);

        IntPtr i = Marshal.AllocHGlobal(len);
        Marshal.Copy(buffer, 0, i, len);
        obj = Marshal.PtrToStructure(i, obj.GetType());

        Marshal.FreeHGlobal(i);
    }

В клиента получавам буфера, но когато клиентът се опитва да използва функцията ByteArrayToStruct, получавам грешка по време на изпълнение.


person user2320928    schedule 14.11.2013    source източник
comment
Как изпращате данните? Можете ли да публикувате код, използван за изпращане/получаване чрез сокет? Изглежда, че публикуваното от вас трябва да работи, грешката може да е причинена от грешно предаване.   -  person Jan Novák    schedule 15.11.2013
comment
И какво е изключението по време на изпълнение?   -  person Jan Novák    schedule 15.11.2013
comment
Не бих използвал obj в извикването на sizeof или извикването на PtrToStructure, а по-скоро се отнасям към самата структура. Обръщението към obj може да е нула, защото е от тип object, а не loginStruct. Освен това има ли причина да не използвате никакви сериализатори и да правите това по трудния начин? Ако двоичният формат е даден, все пак бих препоръчал да използвате BinaryWriter/BinaryReader с поток от памет над тази скорост на маршалинг (кодиране/сигурност).   -  person Roman Gruber    schedule 15.11.2013


Отговори (1)


Добре, имах точно същото предвид, докато се опитвах лесно да анализирам отговорите от патентован сървър. Ето един опростен пример, съобразен с вашия конкретен случай.

Първо имате нужда от няколко разширения, за да направите това много по-лесно. Имайте предвид, че за да направите това, трябва да използвате .NET 3.5 или по-нова версия ИЛИ вижте отговора тук.

Сега, ето какво имам да работя за моя клас разширения:

public static class EndianExtensions {
    /// <summary>
    /// Convert the bytes to a structure in host-endian format (little-endian on PCs).
    /// To use with big-endian data, reverse all of the data bytes and create a struct that is in the reverse order of the data.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="buffer">The buffer.</param>
    /// <returns></returns>
    public static T ToStructureHostEndian<T>(this byte[] buffer) where T : struct {
        GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        T stuff = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
        handle.Free();
        return stuff;
    }

    /// <summary>
    /// Converts the struct to a byte array in the endianness of this machine.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="structure">The structure.</param>
    /// <returns></returns>
    public static byte[] ToBytesHostEndian<T>(this T structure) where T : struct {
        int size = Marshal.SizeOf(structure);
        var buffer = new byte[size];
        GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        Marshal.StructureToPtr(structure, handle.AddrOfPinnedObject(), true);
        handle.Free();
        return buffer;
    }

    public static Dictionary<string, string> GetTypeNames<T>(this T structure) where T : struct {
        var properties = typeof(T).GetFields();

        var dict = new Dictionary<string, string>();

        foreach (var fieldInfo in properties) {
            string[] words = fieldInfo.Name.Split('_');
            string friendlyName = words.Aggregate(string.Empty, (current, word) => current + string.Format("{0} ", word));
            friendlyName = friendlyName.TrimEnd(' ');
            dict.Add(fieldInfo.Name, friendlyName);
        }
        return dict;
    }
}

(Имайте предвид, че част от горното е извадка от източници на CodeProject, всички от които са под CPOL лиценз)

Друго важно нещо, което трябва да се отбележи, е, че разширението GetTypeNames може да се използва за получаване на приятелско име за вашите свойства, ако използвате CamelCaps и долна черта, където искате интервали.

Последната ключова част за осъществяването на тази работа (поне за моя конкретен случай) е да декларирате вашите структури в обратно. Това е така, защото моят сървър използва голям endianness. Може да искате да опитате със и без промяна на endianness - което работи за вас.

Така че, за да използвате това, ето какво трябва да направите:

  1. Декларирайте вашата структура. Тъй като трябваше да го поставя в big endian преди предаване, всички мои са в обратен ред:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Foo {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string User_Name;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string Password;
};

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Bar {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string Password;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string User_Name;
};

Горното предполага, че действителното съдържание на буферите за данни за изпращане и получаване е дефинирано по различен начин, така че във вашия случай просто ще трябва да дефинирате една от тези структури. Имайте предвид, че те са посочени в обратен ред; отново, това се дължи на факта, че трябваше да го предам във формат big-endian.

Сега всичко, което трябва да се направи, е да се създаде структурата за изпращане:

// buffer for storing our received bytes
var barBuf = new byte[64];

// struct that we're sending
var fuz = new Foo {
    User_Name = "username",
    Password = "password"
};

// get the byte equivalent of fuz
var fuzBytes = fuz.ToBytesHostEndian().Reverse().ToArray();

// simulates sock.send() and sock.receive()
// note that this does NOT simulate receiving big-endian data!!
fuzBytes.CopyTo(barBuf, 0);

// do the conversion from bytes to struct
barBuf = barBuf.Reverse().ToArray();
// change this to ToStructureHostEndian<Bar>() if receiving big endian
var baz = barBuf.ToStructureHostEndian<Foo>();
// get the property names, friendly and non-friendly
var bazDict = baz.GetTypeNames();

// change this to typeof(Bar) if receiving big endian
var bazProps = typeof(Foo).GetFields();

// loop through the properties array
foreach (var fieldInfo in bazProps) {
    var propName = fieldInfo.Name;
    // get the friendly name and value
    var fieldName = bazDict[propName];
    var value = fieldInfo.GetValue(baz);

    // do what you want with the values
    Console.WriteLine("{0,-15}:{1,10}", fieldName, value);
}

Важно е да се отбележи, че чрез симулиране на командите sock.Send() и sock.Receive() с помощта на CopyTo(), това не води до голям масив в barBuf. Модифицирах съответно кода, но в случай, че използвате това, за да получавате големи крайни данни, просто променете редовете, посочени в кода.

Надявам се това да помогне. Отне ми много време, за да разбера себе си, тъй като тази информация беше разпръсната в множество източници.

person Forest Kunecke    schedule 23.05.2014