Я написал сообщение об этом в 2004 году, в котором рассматриваются некоторые варианты, доступные для преобразования двоичного потока в структуру памяти .NET. Я разместил его в своем новом блоге, так как старый сайт блога больше не существует.
http://taylorza.blogspot.com/2010/04/archive-structure-from-binary-data.html
В основном у вас есть три варианта
- Использовать указатели памяти в стиле C ++ в C #, для которых требуется ключ / unsafe
- Используйте маршалинг .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 # с помощью параметра / unsafe, а указанная выше структура, которую вы считаете, использует следующий код.
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());