Я написал сообщение об этом в 2004 году, в котором перечислены некоторые параметры, доступные для преобразования двоичного потока в структуру памяти .NET. Я переписал его в своем новом блоге, поскольку старый блог-сайт больше не существует.
http://taylorza.blogspot.com/2010/04/archive-structure-from-binary-data.html
В основном у вас есть три варианта
- Использование C++ указателей памяти стиля в C#, который требует/небезопасного переключателя
- Используйте .NET маршалинг выделить неуправляемый блок памяти, копия байты в неуправляемую память, а затем использовать Marshal.PtrToStructure для маршалирования данных обратно в управляемую кучу, отображающую ее в вашу структуру.
- Используйте BinaryReader для ручного чтения потока байтов и упаковки данных в структуру. Лично это был мой предпочтительный вариант.
При рассмотрении вариантов вы также должны учитывать, как порядок байтов может повлиять на вас.
В качестве примера я буду использовать заголовок IP в качестве примера, поскольку во время публикации я работал с Raw TCP-пакетами.
Вам необходимо определить вашу структуру .NET, к которой будут привязаны двоичные данные. Например, заголовок IP выглядит следующим образом.
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct IpHeader
{
public byte VerLen;
public byte TOS;
public short TotalLength;
public short ID;
public short Offset;
public byte TTL;
public byte Protocol;
public short Checksum;
public int SrcAddr;
public int DestAddr;
}
Обратите внимание, что атрибут StructLayout требуется только для первых двух вариантов, и, конечно, нужно будет установить упаковку, которые необходимы для структуры, которая сериализация с сервера.
Таким образом, в C/C++, указав на блок памяти, содержащий байты данных, которые сопоставляются с структурой C/C++, вы можете использовать следующий бит кода, чтобы просмотреть блок данных в виде структурной части память, где пакет является байтом * в памяти.
IpHeader *pHeader = (IpHeader*)packet;
Выполнение этого же действия с использованием/небезопасной опции C#, а структура, определенная выше, вы используете следующий код.
IpHeader iphdr;
unsafe
{
fixed (byte *pData = packet)
{
iphdr = *(IpHeader*)pData;
}
}
//Use iphdr...
Опция сортировочный будет выглядеть следующим образом
IntPtr pIP = Marshal.AllocHGlobal(len);
Marshal.Copy(packet, 0, pIP, len);
iphdr = (IpHeader)Marshal.PtrToStructure(pIP, typeof(IpHeader));
Marshal.FreeHGlobal(pIP);
И, наконец, вы можете использовать BinaryReader сделать это полностью в управляемом коде.
MemoryStream stm = new MemoryStream(packet, 0, len);
BinaryReader rdr = new BinaryReader(stm);
iphdr.VerLen = rdr.ReadByte();
iphdr.TOS = rdr.ReadByte();
iphdr.TotalLength = rdr.ReadInt16();
iphdr.ID = rdr.ReadInt16();
iphdr.Offset = rdr.ReadInt16();
iphdr.TTL = rdr.ReadByte();
iphdr.Protocol = rdr.ReadByte();
iphdr.Checksum = rdr.ReadInt16();
iphdr.SrcAddr = rdr.ReadInt32();
iphdr.DestAddr = rdr.ReadInt32();
Как я уже говорил ранее, возможно, вам придется рассмотреть порядок байтов.Например, приведенный выше код не совсем корректен, поскольку IpHeader не использует тот же порядок байтов, что и предполагаемый ReadInt16. ReadInt32 и т. Д. Решение проблемы с помощью вышеупомянутого решения так же просто, как использование IPAddress.NetworkToHostOrder.
iphdr.VerLen = rdr.ReadByte();
iphdr.TOS = rdr.ReadByte();
iphdr.TotalLength = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
iphdr.ID = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
iphdr.Offset = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
iphdr.TTL = rdr.ReadByte();
iphdr.Protocol = rdr.ReadByte();
iphdr.Checksum = IPAddress.NetworkToHostOrder(rdr.ReadInt16());
iphdr.SrcAddr = IPAddress.NetworkToHostOrder(rdr.ReadInt32());
iphdr.DestAddr = IPAddress.NetworkToHostOrder(rdr.ReadInt32());
Ах спасибо! Я пошел и попробовал BinaryReader, и был полностью озадачен тем, почему цифры ошибочны. Увидев, что, похоже, нет никаких других управляемых альтернатив, я думаю, что я просто буду читать прямо из потока и заполнять классы. – cpf