views:

80

answers:

2

I'm trying to send a network packet of data to a hardware device. I would like to use a slick, object oriented approach so I can work with it at a high level. The packet has several fields of variable length. Obviously the byte layout is very particular.

Here's a struct representing the packet I need to send:

[StructLayout(LayoutKind.Sequential)]
public struct Packet
{
   public UInt16 Instruction;
   public UInt16 Length; //length of data field
   public UInt32 SessionHandle;

   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
   public byte[] SenderContext;

   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
   public byte[] MessageData;
}

MessageData[] can be any length, I'm fixing it to 8 bytes as a starter.

Here's my attempt at creating a byte[] to send it via Socket:

public static byte[] ToBytes(Packet ep)
{
   Byte[] bytes = new Byte[Marshal.SizeOf(typeof(Packet))];
   GCHandle pinStructure = GCHandle.Alloc(ep, GCHandleType.Pinned);
   try
   {
      Marshal.Copy(pinStructure.AddrOfPinnedObject(), bytes, 0, bytes.Length);
      return bytes;
   }
   finally
   {
      pinStructure.Free();
   }
}

But I receive:

ArgumentException : Object contains non-primitive or non-blittable data.

I thought setting SizeConst in the struct would take care of this? Anyways, I'm more lost than this because the hardware device is expecting a variable length packet, and I'd like to take advantage of that.

I can manually put together the packet byte-by-byte and everything works great. But I know there has got to be a better way, and that I must be going down the wrong path.

Any ideas?

+1  A: 

The CLR will not allow you to use a struct that has a field that overlaps a reference type. The two arrays in your case. It is quite incompatible with the garbage collector, it cannot keep track of the object reference. And would be quite unsafe as it allows a backdoor into the heap, manipulating the value of the object reference directly.

There's no point in doing so in your specific case as both arrays overlap and are exactly the same size. One will get the job done just fine.

Hans Passant
That makes sense, thank you. If you were me, how would you structure the I/O so that you could send the external device packets of data with fixed values (like enumerations) describing the packet, and a single byte[] of variable size?
bufferz
BinaryWriter is the boring but always works solution. Marshal.StructureToPtr followed by Marshal.Copy is a bit hacky. Pinning the structure is not good, its layout would be compatible only by accident. Note that should use Pack=1 in your [StructLayout].
Hans Passant
A: 

The problem is that arrays are not blittable nor are they packed into the struct. They are reference types afterall. So the struct really just contains a reference to the array object which is on the heap. As far am aware the MarshalAs attribute does not affect the layout of members. It only affects how the members are marshaled during interop routines.

Another observation I have is that you are actually pinning the boxed version of the struct and not the actual struct. A value type, of course, lives on the stack so there is no need to pin it. Maybe you knew that already and that this was the method you chose to extract an IntPtr out of it. That is fine, but you could more efficiently and appopriately accomplish the same thing by using unsafe code or the Marshal.StructureToPtr method.

I think your best bet is to create a separate byte array and use the Array.Copy method and BitConverter class.

Brian Gideon