Пакет 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 и подчеркивания там, где вам нужны пробелы.

Последний ключевой момент для выполнения этой работы (по крайней мере, в моем конкретном случае) - объявить ваши структуры в reverse. Это потому, что мой сервер использовал обратный порядок байтов. Вы можете попробовать это с изменением порядка байтов и без него - в зависимости от того, что вам подходит.

Итак, чтобы на самом деле это использовать, вот что вы делаете:

  1. Объявите свою структуру. Так как перед передачей мне нужно было перевести его в обратный порядок байтов, все мои обратные:

[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