2013-11-14 3 views
0

Я пытаюсь отправить буфер с сервера на клиент, который я сделал сам. Он работает с сокетами на TCP.C# pack 1 StructLayout networking

У меня есть структура, которая мне нужно отправить:

[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, я получил ошибку времени выполнения.

+0

Как вы отправляете данные? Можете ли вы отправить код, используемый для отправки/получения через сокет? Кажется, что вы опубликовали сообщение, ошибка может быть вызвана неправильной передачей. –

+0

А что такое исключение во время выполнения? –

+0

Я не использовал бы «obj» в вызове sizeof или вызове PtrToStructure, а скорее обращался к самой структуре. Ссылка obj может быть нулевой, поскольку она имеет тип «объект», а не «loginStruct». Кроме того, есть ли причина, почему вы не используете сериализаторы и делаете это «трудным путем»? Если двоичный формат задан, я бы порекомендовал использовать BinaryWriter/BinaryReader с memystream над этим алгоритмом (кодированием/безопасностью) speedbump. –

ответ

0

Хорошо, что я имел ту же самую точку зрения, пытаясь легко разобрать ответы с проприетарного сервера. Вот упрощенный пример, скорректированный на ваш конкретный случай.

Прежде всего вам нужно несколько расширений, чтобы сделать это намного проще. Обратите внимание, что для этого вам нужно использовать .NET 3.5 или выше или см. Ответ here.

Итак, вот что я начала работать для моего расширений класса:

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

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

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

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

  1. Объявить свою структуру. Так как мне нужно, чтобы поместить его в большой 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; 
}; 

Теперь выше предполагает, что фактическое содержание буферах отправки и приема данных определяются по-разному так что в вашем случае вам просто нужно определить одну из этих структур. Обратите внимание, что они указаны в обратном порядке; опять же, это связано с тем, что мне нужно было передать его в формате большой буквы.

Теперь все, что должно быть сделано, это создать-структуру для отправки:

// 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(), это не приводит к большому массиву endian в barBuf. Я изменил код соответствующим образом, но в случае, если вы используете его для получения больших данных, просто измените строки, указанные в коде.

Надеюсь, это поможет. Мне потребовалось много времени, чтобы разобраться в себе, поскольку эта информация была разбросана по нескольким источникам.