从字节序列处理动态大小的结构体



我正在处理来自网络流的数据包解析。发送端已经实现,我只实现客户端,所以数据应该按原样解析。以前所有的包都是常量大小的,所以我这样做:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe ref struct MyConstSizedStruct
{
//example structure
public uint Data1;
public byte Data2;
public fixed byte Version[6];
}

所以这个的大小是4 + 1 + 6 = 11。现在我接收到要缓冲的数据,并将其指向一个结构体。

public unsafe void HandleMyConstSizedStruct(ushort length, byte[] data)
{
MyConstSizedStruct pkt;
fixed (byte* ptr = &data[0])
pkt = *(MyConstSizedStruct*)ptr;
//dealing with data
}

在这一点上一切都很好,但现在我得到了更复杂的东西:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct MyDynamicSizedStruct
{
public uint Data1;
public uint Data2;
//here we have null-terminated utf-8 string of unknown length
public char* User;
public uint Data3;
public uint Data4;
public byte Data5;
public byte[] RestOfData;
}

所以问题是:现在数据包是动态大小的,大小作为前缀ushort从网络接收,但简单的转换不起作用,因为:

  1. 以null结尾的字符串
  2. byte[]被认为是托管类型,所以现在struct也被认为是托管的,所以我不能强制转换它

有一个很好的方法来解释这样的结构,或者我应该处理这个特殊情况下的BinaryReader ?

编辑:一些想法:

  1. char*的偏移量总是8,所以我们可以计算char buffer的长度作为0字节的索引,从8开始。
  2. 以某种方式取代char*Span<char>设置这个缓冲区的长度在一个结构体?或移位数据3通过计算偏移可能与一些指针分配?
  3. 设置正确的字符缓冲区填充后,读取byte[]数组应该不是问题,因为我们知道总大小,我们可以执行totalsize - 17 /*size of all DataX*/ - /*char size*/来获取byte[]大小

好的,所以我找到了一些解决方案(可能不是最好的,但对我来说仍然比BinaryReader更好。
假设我们有这个结构体

[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct MyStruct
{
public uint Data1;
public uint Data2;
public char* user;
public uint Data3;
public uint Data4;
public byte* restOfData;
}

我们有一个恒定的偏移量8(4 + 4),直到char*和8从char*结束到byte*。所以我们可以使用MemoryMarshal类。

const int offset = 8;
MyStruct pkt;
fixed (byte* ptr = &buffer[0])
{
pkt = *(MyStruct*)ptr;
} //here we get all prefix fields as normal.
//Get index of start of field after char*
int index = Array.IndexOf<byte>(buffer, 0, offset) + 1;
var memory = MemoryMarshal.CreateFromPinnedArray(buffer, offset, size - offset);
index -= offset;
//Parse all fields
string user = Encoding.UTF8.GetString(memory.Span[0..index]);
pkt.Data3 = BitConverter.ToUInt32(memory.Span[index..]);
pkt.Data4 = BitConverter.ToUInt32(memory.Span[(index+4)..]);
//Set rest of data
fixed (byte* ptr = memory.Span[(index + 8)..])
pkt.restOfData = ptr;

最新更新