Search code examples
c#structmarshallingintptr

Marshalling IntPtrs within structs in C#


I intend to model a C# class for parsing custom protocol data I receive as a raw buffer. The buffer is already at hand as a byte array.

This parser has to cope with varying field layouts, hence some of the protocol elements are IntPtrs.

When I try to access a pointer within a struct after mapping the raw buffer into it, I run into an access violation.

I made up a simple example which shows the crucial part:

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);
    }
  }
}

I just try to wrap my head around this issue, but to no avail. To my understanding, the IntPtr must be marshalled seperately, but I don't find any hints how this could be done in a clean way.

Any pointers welcome. Thank you for your time.


Solution

  • There's a fundamental flaw in the approach, based on a possible misunderstanding what a pointer really means. A pointer, IntPtr in managed code, is simply the address of a location in memory. You read what is pointed-to with the kind of code you already use, like Marshal.PtrToStructure().

    A problem with pointers is that they are only valid in one process. Every process has its own virtual memory address space. With an additional restriction in managed code, the garbage collector can randomly move an object from one location to another. There are workarounds for that, you could pinvoke ReadProcessMemory() to read data from another process. And you work around the garbage collector behavior by pinning an object, GCHandle.Alloc()

    But these are workarounds that don't belong in a custom serialization scheme. An obvious failure mode for ReadProcessMemory() is just not knowing which process has the data. Or not having sufficient rights to use it. Or the process running on another machine. Pinning pointers is flawed because there is no good guarantee that you'll ever un-pin them.

    So any serialization approach solves this problem by flattening the data, eliminating pointers by replacing them with the pointed-to data. And resurrect the object graph in the deserializer. The job done by classes like BinaryFormatter, XmlSerializer, DataContractSerializer and DataContractJsonSerializer. And 3rd party ones like Mark's favorite. Don't write your own, there are so many of them because it is hard to get right.