1

我打算建模一个 C# 类来解析我作为原始缓冲区接收的自定义协议数据。缓冲区已经作为字节数组存在。

这个解析器必须处理不同的字段布局,因此一些协议元素是 IntPtrs。

当我在将原始缓冲区映射到结构后尝试访问结构内的指针时,我遇到了访问冲突。

我做了一个简单的例子来展示关键部分:

namespace CS_StructMarshalling
{
  class Packet
  {  
    public PacketStruct Fields;

    [StructLayout(LayoutKind.Explicit)]
    public struct PacketStruct
    {
      [FieldOffset(0)] public UInt16 Flags;
      [FieldOffset(2)] public IntPtr Data;
    }

    public Packet()
    {
      Fields = new PacketStruct();
    }

    public void DecompileBinaryBuffer(ref Byte[] byteBuffer)
    {
      int size = Marshal.SizeOf(typeof(PacketStruct)); 
      IntPtr ptr = Marshal.AllocHGlobal(size);

      Marshal.Copy(byteBuffer, 0, ptr, size);

      this.Fields = (PacketStruct)Marshal.PtrToStructure(ptr, typeof(PacketStruct));
      Marshal.FreeHGlobal(ptr);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      byte[] rawBuffer = new byte[] { 1, 2, 65, 66, 67, 68, 69, 70 };

      Packet testPkt = new Packet();
      testPkt.DecompileBinaryBuffer(ref rawBuffer);

      UInt16 testFlags = testPkt.Fields.Flags;
      String testData = Marshal.PtrToStringAnsi(testPkt.Fields.Data, 6);
    }
  }
}

我只是试图解决这个问题,但无济于事。据我了解, IntPtr 必须单独编组,但我没有找到任何提示如何以干净的方式完成此操作。

欢迎任何指点。感谢您的时间。

4

3 回答 3

1

你已经很好地打开了它;问题是数据;您有以下硬编码数据:

  • 标志 = 513
  • 数据 = 1145258561(如果使用 x86,则为接下来的 4 个字节)

问题很简单:指针值“1145258561”无效,除了可能在该数字来自的早已死去的 AppDomain 中。

您不能强行进入不属于您的数据。

此外,在 x64 上它会更快出错,因为缓冲区是 8 个字节,而结构需要 10 个字节。

另外:您的使用ref不正确,您可能可以避免HGlobal使用stackalloc.

于 2012-05-16T10:20:59.313 回答
1

您正在解码字符数据,就好像它是指向字符数据的指针一样,因此它将指向内存中不包含字符数据的任意位置,并且很可能位于程序所在的地址空间部分之外允许使用。您根本无法将数据解码为指针,因为它不是指针。

将数据转换为它们的类型。由于字符是 8 位代码,您需要使用 8 位字符集对其进行解码:

ushort testFlags = BitConverter.ToUInt16(rawBuffer, 0);
string testData = Encoding.ASCII.GetString(rawBuffer, 2, rawBuffer.Length - 2);

结果:

513
ABCDEF
于 2012-05-16T10:27:42.273 回答
1

基于对指针真正含义的可能误解,该方法存在一个根本缺陷。托管代码中的指针 IntPtr 只是内存中某个位置的地址。您可以阅读您已经使用的那种代码所指向的内容,例如 Marshal.PtrToStructure()。

指针的一个问题是它们只在一个进程中有效。每个进程都有自己的虚拟内存地址空间。通过托管代码的附加限制,垃圾收集器可以将对象从一个位置随机移动到另一个位置。有一些解决方法,您可以调用 ReadProcessMemory() 从另一个进程读取数据。然后通过固定对象 GCHandle.Alloc()来解决垃圾收集器的行为

但这些是不属于自定义序列化方案的解决方法。ReadProcessMemory() 的一个明显失败模式就是不知道哪个进程拥有数据。或者没有足够的权利使用它。或者在另一台机器上运行的进程。固定指针是有缺陷的,因为不能很好地保证您会取消固定它们。

因此,任何序列化方法都通过扁平化数据来解决这个问题,通过将指针替换为指向的数据来消除指针。并在反序列化器中复活对象图。由 BinaryFormatter、XmlSerializer、DataContractSerializer 和 DataContractJsonSerializer 等类完成​​的工作。还有像马克最喜欢的第 3 方。不要自己写,它们太多了,因为很难写好。

于 2012-05-16T11:03:59.837 回答