Nov 06, 2006

Marshalling problem, explained

I commented this days ago, now here comes the explanation.

The idea is the following, you have to send through sockets a marshalled structure, this structure will be received by socket server written in C. Then, for example I have:

#pragma pack (1)
struct sample {
int index;
int length; /* Indicates value length */
char *value; /* This value might be any value */
};
typedef struct sample sample_t;
#pragma end

That C structure, in C# is, after marshalling:

[StructLayout (LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct Sample
{
public int Index;
public int DataLength;
[MarshalAs (UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 1)]
public byte []Data;
}

We have our marshalled C# structure. Did you notice the SizeConst value? Well that SizeConst, will, when using Marshal.SizeOf (typeof (Sample)), return a structure known-bytes size. If you are planning to use real-sized structure you MUST marshall them, and there comes the pain. Why? Because you actually need to know about bytes and size of each element send or received by differents processes, in this example, through IP sockets.

C structure uses a dynamic array and I want to use it that way. By setting the size to 1 in C# structure forces me to use 1 byte instead. Really problematic isn't it? Until today I haven't yet found the way to do it automatically. My solution is to use Array.Copy to copy the marshalled value (value contained in the byte[]) after the full structure was marshalled, it works, but isn't too fancy, and when using a Structure that contains a byte[] and setting into that byte[] another Structure that also contains byte[] the problems increase because you need to Array.Copy each one of them.

By the way, I'm using the "google solution" to marshall .NET structures to byte[], am not sure who is the original author of the following code, but seems to be solution to convert from any marshalled structure to bytes array, and viceversa:

public static object RawDeserialize (byte[] rawdatas, Type anytype) {
int rawsize = Marshal.SizeOf (anytype);
if (rawsize > rawdatas.Length)
return null;
GCHandle handle = GCHandle.Alloc (rawdatas, GCHandleType.Pinned);
IntPtr buffer = handle.AddrOfPinnedObject ();
object retobj = Marshal.PtrToStructure (buffer, anytype);
handle.Free ();
return retobj;
}

public static byte[] RawSerialize (object anything)
{
int rawsize = Marshal.SizeOf (anything);
byte[] rawdatas = new byte [rawsize];
GCHandle handle = GCHandle.Alloc (rawdatas, GCHandleType.Pinned);
IntPtr buffer = handle.AddrOfPinnedObject ();
Marshal.StructureToPtr (anything, buffer, false);
handle.Free ();
return rawdatas;
}

Be aware that are some tips&tricks while marshalling and you must follow each one of them. Because we are living in managed world and unmanaged is almost a sin.