С наступающим Новым годом! Форум программистов, компьютерный форум, киберфорум
Наши страницы
C# для начинающих
Войти
Регистрация
Восстановить пароль
 
Рейтинг 4.81/26: Рейтинг темы: голосов - 26, средняя оценка - 4.81
natrox
12 / 16 / 5
Регистрация: 13.04.2011
Сообщений: 148
1

Преобразование класса в массив байт

19.07.2013, 07:18. Просмотров 4973. Ответов 15
Метки нет (Все метки)

Доброго времени суток!
Задача следующая. Через сокет приходит пакет данных в виде байтового массива (datagrab). На серверном приложении до преобразования, пакет существует в виде класса аналог которого на C# будет выглядеть примерно так:
C#
1
2
3
4
5
6
7
8
9
10
public class paket
    {
        public byte type = 0;
        public byte cmd = 0;
        public byte len = 0;
        public byte res = 0;
        public char[] login = new char[63];
        public double datetime;
        public char[] hash = new char[20];
    }
Необходимо преобразовать массив байт в формат такого класса, совершить над ним некие операции (допустим изменить поля), класс преобразовать в массив байт и отправить ответ серверу.
У меня проблема с преобразованием. Просто один тип преобразовать проблем не возникает, а вот с классом не понятно как дело обстоит.
За ранее благодарю.
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
19.07.2013, 07:18
Ответы с готовыми решениями:

Преобразование изображения в массив байт
Уважаемые коллеги, подскажите как из изображения(тип любой) получить массив...

Вычесть из одного массива байт другой массив байт
Здравствуйте! Подскажите что нужно сделать чтобы из одного массива байт...

Преобразование байт в десятичные значения
Здравствуйте, прошу помощи в понимании наверное самых основ. Получаю из сокета...

Как перевести массив байт в массив символов, используя определённую кодировку?
Всем доброго времени суток, ув. форумчане! Делаю алгоритм LFSR, почти...

Преобразование уинт\инт числа в байт массив
Здравствуйте. Необходимо преобразовать uint\int числа в байт массив БЕЗ...

15
vialet
57 / 57 / 11
Регистрация: 04.03.2010
Сообщений: 244
19.07.2013, 08:16 2
использовать сериализацию, для этого пометить класс атрибутом
C#
1
[Serializable()]
1
natrox
12 / 16 / 5
Регистрация: 13.04.2011
Сообщений: 148
19.07.2013, 09:24  [ТС] 3
Цитата Сообщение от vialet Посмотреть сообщение
использовать сериализацию, для этого пометить класс атрибутом
C#
1
[Serializable()]
Вопрос в том, подойдет ли сериализация в данном случае? Проблема в том что сервер принимает пакет определенного количества байт. На сколько мне известно, при сериализации размер пакета в байтах будет больше чем реально занимаемое пространство.
Есть вариант сделать не класс, а структуру и явно указать размер выделяемой памяти. Вот так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[StructLayout(LayoutKind.Explicit)]
        public struct st
        {
            [FieldOffset(0)]
            public byte type; //1 byte
            [FieldOffset(1)]
            public byte cmd; //1 byte
            [FieldOffset(2)]
            public byte len; //1 byte
            [FieldOffset(3)]
            public byte res; //1 byte
            [FieldOffset(4)]
            public char[] login; //64 byte
            [FieldOffset(68)]
            public double datetime; //8 byte
            [FieldOffset(76)]
            public char[] hash;//21 byte
        }
Но тут всплывает проблема что в структуре нельзя инициализировать типы, а у меня поле hash должно быть 21 байт. В данном случае пакет получается 80 байт, так как последнее поле выравнивается к формату 4 байта. Порядок полей я так же изменить не могу, потому-что сервер не сможет правильно прочитать пакет данных.

Во избежание лишних вопросов, не я придумал такой пакет. Мне просто нужно следовать установленному формату.

Добавлено через 6 минут
у меня пакет данных должен быть размером в 97 байт.

Добавлено через 20 минут
Проблема решилась вот так:
C#
1
2
[FieldOffset(76), MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public char[] hash;//21 byte
Осталось теперь только перевести в массив байт чтобы размер не изменился. Сериализация не подходит. Если кто знает как это реализовать вручную, то прошу дать пример.

Добавлено через 11 минут
Есть мысль что можно каждое поле по отдельности конвертировать с помощью BitConverter.GetBytes, а потом собрать все результаты в один байтовый массив как-будто класс полностью преобразовали. Но это в теории, как это реализовать я что-то даже не представляю. Может кто знает как сделать это или проще способ?
0
Евгений В
876 / 645 / 130
Регистрация: 01.03.2010
Сообщений: 1,213
19.07.2013, 09:33 4
natrox,
Здесь была похожая тема
как сохранить в массиве байт разнотипную информацию, а потом извлечь ее
1
natrox
12 / 16 / 5
Регистрация: 13.04.2011
Сообщений: 148
19.07.2013, 11:45  [ТС] 5
Цитата Сообщение от Евгений В Посмотреть сообщение
Попытка сделать таким методом успеха не принесла к сожалению

Структура:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[StructLayout(LayoutKind.Explicit, Size=96)]
        public struct st
        {
            [FieldOffset(0)]
            public byte type; //1 byte
            [FieldOffset(1)]
            public byte cmd; //1 byte
            [FieldOffset(2)]
            public byte len; //1 byte
            [FieldOffset(3)]
            public byte res; //1 byte
            [FieldOffset(4)]
            public char[] login; //64 byte
            [FieldOffset(68)]
            public double datetime; //8 byte
            [FieldOffset(76), MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public char[] hash;//20 byte
        }
Вот я пробую преобразовать в массив байт, но размеры почему-то меняются. Может кто объяснит почему так происходит?
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public byte[] DatagrabToArray()
        {
            byte[] bytes1 = BitConverter.GetBytes(datagrab.type);
            byte[] bytes2 = BitConverter.GetBytes(datagrab.cmd);
            byte[] bytes3 = BitConverter.GetBytes(datagrab.len);
            byte[] bytes4 = BitConverter.GetBytes(datagrab.res);
            byte[] bytes5 = Encoding.Default.GetBytes(datagrab.login);
            byte[] bytes6 = BitConverter.GetBytes(datagrab.datetime);
            byte[] bytes7 = Encoding.Default.GetBytes(datagrab.hash);
 
            Stream ms = new MemoryStream();
            ms.Write(bytes1, 0, bytes1.Length);
            ms.Write(bytes2, 0, bytes2.Length);
            ms.Write(bytes3, 0, bytes3.Length);
            ms.Write(bytes4, 0, bytes4.Length);
            ms.Write(bytes5, 0, bytes5.Length);
            ms.Write(bytes6, 0, bytes6.Length);
            ms.Write(bytes7, 0, bytes7.Length);
 
            richTextBox1.AppendText(bytes1.Length.ToString() + " \n");//2
            richTextBox1.AppendText(bytes2.Length.ToString() + " \n");//2
            richTextBox1.AppendText(bytes3.Length.ToString() + " \n");//2
            richTextBox1.AppendText(bytes4.Length.ToString() + " \n");//2
            richTextBox1.AppendText(bytes5.Length.ToString() + " \n");//8
            richTextBox1.AppendText(bytes6.Length.ToString() + " \n");//8
            richTextBox1.AppendText(bytes7.Length.ToString() + " \n");//40
 
            ms.Seek(0, System.IO.SeekOrigin.Begin);
 
            byte[] mas = new byte[ms.Length];
            ms.Read(mas, 0, Convert.ToInt32(ms.Length));
 
            return mas;
        }
Т.е. такой массив мне уже не подойдет, по той причине что сервер не сможет его правильно распознать.

Добавлено через 40 минут
BitConverter.GetBytes возвращает 16 битовое значение. Поэтому получается не 1, а 2 байта на выходе. А мне надо чтобы остался 1 байт как в структуре.
0
MasMaX
7 / 7 / 2
Регистрация: 07.02.2012
Сообщений: 71
19.07.2013, 13:31 6
Цитата Сообщение от natrox Посмотреть сообщение
Вопрос в том, подойдет ли сериализация в данном случае? Проблема в том что сервер принимает пакет определенного количества байт. На сколько мне известно, при сериализации размер пакета в байтах будет больше чем реально занимаемое пространство.
Можно применить сериализацию, и получить на выходе файл. А потом этот файл уже по пакетам (в ручную разбить на любое количество байт) передавать на сервер.
0
Psilon
Master of Orion
Эксперт .NET
6013 / 4866 / 902
Регистрация: 10.07.2011
Сообщений: 14,477
Записей в блоге: 5
Завершенные тесты: 4
19.07.2013, 20:14 7
Мб выравнивание?

Добавлено через 2 минуты
Попробуйте так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    [StructLayout(LayoutKind.Explicit, Size = 96, Pack = 1)]
    public struct Struct
    {
        [FieldOffset(0)]
        public byte type; //1 byte
        [FieldOffset(1)]
        public byte cmd; //1 byte
        [FieldOffset(2)]
        public byte len; //1 byte
        [FieldOffset(3)]
        public byte res; //1 byte
        [FieldOffset(4)]
        public char[] login; //64 byte
        [FieldOffset(68)]
        public double datetime; //8 byte
        [FieldOffset(76), MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
        public char[] hash;//20 byte
    }
0
Konctantin
940 / 744 / 171
Регистрация: 12.04.2009
Сообщений: 1,700
19.07.2013, 21:28 8
Сериализация не пройдет. потому что там отправляются метаданные.
Если вы хотите "кидатся чистыми данными" вам надо структуру привести в массив байт.
Как это сделать?
* есть несколько способов, самым быстрым является связка Marshal + unsafe код.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        unsafe byte[] GetBytes<T>(T obj) where T : struct
        {
            var size   = Marshal.SizeOf(typeof(T));
            var buffer = new byte[size];
 
            fixed (void* pointer = buffer)
            {
                Marshal.StructureToPtr(obj, new IntPtr(pointer), false);
                return buffer;
            }
        }
 
        unsafe T CreateStruct<T>(byte[] buffer) where T : struct
        {
            fixed (void* pointer = buffer)
            {
                return (T)Marshal.PtrToStructure(new IntPtr(pointer), typeof(T));
            }
        }
Добавлено через 4 минуты
Рабочий пример:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
using System.Runtime.InteropServices;
 
namespace FastStructWriter
{
    public struct MyStruct
    {
        public int f1;
        public int f2;
        public int f3;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
        public float[] f4;
    }    
    class Program
    {
        static unsafe void Main(string[] args)
        {
            var ms = new MyStruct() { f1 = 5, f2 = 6, f3 = 7, f4 = new float[] { 43.5f, 47.5f, 55.5f, 45.85f, 95.8f } };
 
            var b = GetBytes<MyStruct>(ms);
            var m = CreateStruct<MyStruct>(b);
 
            Console.ReadLine();
        }
 
        static unsafe byte[] GetBytes<T>(T obj) where T : struct
        {
            var size   = Marshal.SizeOf(typeof(T));
            var buffer = new byte[size];
 
            fixed (void* pointer = buffer)
            {
                Marshal.StructureToPtr(obj, new IntPtr(pointer), false);
                return buffer;
            }
        }
 
        static unsafe T CreateStruct<T>(byte[] buffer)
        {
            fixed (void* pointer = buffer)
            {
                return (T)Marshal.PtrToStructure(new IntPtr(pointer), typeof(T));
            }
        }    
    }
}
ЗЫ. Не забывайте sizeof(char) == 2
2
natrox
12 / 16 / 5
Регистрация: 13.04.2011
Сообщений: 148
20.07.2013, 08:09  [ТС] 9
Цитата Сообщение от MasMaX Посмотреть сообщение
Можно применить сериализацию, и получить на выходе файл. А потом этот файл уже по пакетам (в ручную разбить на любое количество байт) передавать на сервер.
Нет, сериализация тут не подходит по той причине что я десериализовать не смогу на сервере пакет. Серверное приложение мне не доступно, я лишь могу ему отправить определенный пакет и получить от него ответ в виде определенного пакета данных.

Добавлено через 3 минуты
Konctantin, огромное спасибо за совет. Попробую сделать так как вы посоветовали. Думаю мне это подойдет.
0
natrox
12 / 16 / 5
Регистрация: 13.04.2011
Сообщений: 148
22.07.2013, 09:32  [ТС] 10
Konctantin, попытался применить ваш метод и получил ошибку:
"Тип указанного массива не совпадает с ожидаемым."

на вот эту строку
C#
1
Marshal.StructureToPtr(obj, new IntPtr(pointer), false);
почему так получается? поля в структуре не сосуществуют типу который может преобразовать Marshal или почему так получилось?

Добавлено через 17 часов 51 минуту
А с помощью BinaryWriter есть вариант это сделать? Даже если каждое поле в отдельности записывать в массив байт придется, мне такое тоже подойдет. Уже любой вариант подойдет, лишь бы работало

Добавлено через 58 секунд
Если кто может дать пример преобразования моей структуры в массив байт в ручную, буду очень благодарен

Добавлено через 4 часа 0 минут
Ситуация следующая:

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

Что можно сделать в таком случае?

Код структуры на данный момент:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        [StructLayout(LayoutKind.Explicit, Pack=1)]
        public struct st
        {
            [FieldOffset(0)]
            public byte type; //1 byte
            [FieldOffset(1)]
            public byte cmd; //1 byte
            [FieldOffset(2)]
            public byte len; //1 byte
            [FieldOffset(3)]
            public byte res; //1 byte
            [FieldOffset(4), MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
            public char[] login; //64 byte
            [FieldOffset(68)]
            public double datetime; //8 byte
            [FieldOffset(76), MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public char[] hash;//20 byte
        }
0
Psilon
Master of Orion
Эксперт .NET
6013 / 4866 / 902
Регистрация: 10.07.2011
Сообщений: 14,477
Записей в блоге: 5
Завершенные тесты: 4
22.07.2013, 11:19 11
C#
1
2
3
4
            string login = "mylogin";
 
            var struct64Fielld = new char[64];
            Array.Copy(login.ToCharArray(), struct64Fielld, login.Length);
Добавлено через 2 минуты
C#
1
2
3
            string login = "mylogin";
 
            string struct64Fielld = new string((char) 0, 64).Insert(0, login);
еще много способов.
1
Konctantin
940 / 744 / 171
Регистрация: 12.04.2009
Сообщений: 1,700
22.07.2013, 11:24 12
1) не заморачивайтесь с оффсетами, они тут вам не нужны, достаточно 1 атритута
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[StructLayout(LayoutKind.Sequential)]
public struct st
{
    public byte type;
    public byte cmd;
    public byte len;
    public byte res;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
    public char[] login;
    public double datetime;
    [ MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
    public char[] hash;
 
    public string Login
    {
        get { return string.Join("", login.TakeWhile(n => n != 0)); }
        set { login = value.PadRight(64, '\0').ToArray(); }
    }
}
2) вы можете дописывать нулями незанятое место.
Но так как это не практично, то вам надо сделать свой обработчик и построитель пакетов.
2
Psilon
22.07.2013, 11:29
  #13

Не по теме:

Konctantin, чет я тоже про свойство подумал :)

0
Konctantin
940 / 744 / 171
Регистрация: 12.04.2009
Сообщений: 1,700
22.07.2013, 11:51 14
вобще-то забивать массив нулями не есть хорошо, это же лишний размер.
Я бы поступил так:
Создавал бы структуру, в начале писал бы нативные типы, а в конец засунул бы строки.
Далее, ограничивал бы структуру размером нативных типов, а строки дописывал бы в конец пакета.
В место строк указал бы их смещения в пакете.
при разборе обратно, получили нативные типы, а потом прочитал бы строки из оставшегося куска.

В реализации немного сложнее, но суть остается та же.

Вот только при реализации "сложных" пакетов такой фокус не получится.
1
Anklav
443 / 301 / 47
Регистрация: 23.01.2013
Сообщений: 641
Завершенные тесты: 2
22.07.2013, 12:28 15
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
            [FieldOffset(0)]
            public byte type; //1 byte
            [FieldOffset(1)]
            public byte cmd; //1 byte
            [FieldOffset(2)]
            public byte len; //1 byte
            [FieldOffset(3)]
            public byte res; //1 byte
            [FieldOffset(4), MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
            public char[] login; //64 byte
            [FieldOffset(68)]
            public double datetime; //8 byte
            [FieldOffset(76), MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public char[] hash;//20 byte
Вам же писали, char занимает 2 байта, а SizeConst это длинна не в байтах, а в количестве элементов, в итоге получается наезд данных друг на друга. Вместо char[] укажите byte[], и выполняйте преобразование с помощью класса Encoding в массив байт.
1
natrox
12 / 16 / 5
Регистрация: 13.04.2011
Сообщений: 148
23.07.2013, 04:55  [ТС] 16
Проблема решилась благодаря добавлению пустых значений в конец строки.
C#
1
2
3
login = st_fr.textBox2.Text;
                login = string.Join("", login.TakeWhile(n => n != 0)); 
                login = login.PadRight(64, '\0');
0
23.07.2013, 04:55
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
23.07.2013, 04:55

Преобразование 1 класса в другой
Подскажите возможно ли имея 2 одинаковых класс как ни буть приравнять один к...

Преобразование класса к субклассу
using System; using System.Collections.Generic; using System.Linq; using...

Массив байт в переменную
Здравствуйте , вот я &quot; храню &quot; файл byte qwe = ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
16
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2018, vBulletin Solutions, Inc.
Рейтинг@Mail.ru