Some of the C# code I have been writing communicates via TCP/IP with legacy C++ applications. Some codes use a raw packet format where C/C++ structures are passed back and forward.
Example of what the legacy code could look like:
#pragma pack(1) typedef struct { int id; char[50] text; } MESSAGE; // Send a message MESSAGE msg; msg.id = 1; strcpy(msg.text, "This is a test"); send(socket, (char*)&msg); // Receive a message char buffer[100]; recv(socket, buffer, 100); MESSAGE* msg = (MESSAGE*)buffer; printf("id=%d\n", msg->id); printf("text=%s\n", msg->text);
The problem was born with was how to receive and handle this kind of message in a C#. One way is to use BitConverter and Encoding.ASCII to grab the data field by field. This is slow, inclined to errors and easy to break of modifications are made in the future.
The Best Practice is to marshal the byte array to a C# structure. Here is an example of how to do that marshaling:
using System; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential, Pack=1)] struct Message { public int id; [MarshalAs (UnmanagedType.ByValTStr, SizeConst=50)] public string text; } void OnPacket(byte[] packet) { GCHandle pinnedPacket = GCHandle.Alloc(packet, GCHandleType.Pinned); Message msg = (Message)Marshal.PtrToStructure( pinnedPacket.AddrOfPinnedObject(), typeof(Message)); pinnedPacket.Free(); }
In this example “GCHandle.Alloc” start call pins the byte[] in memory, so the garbage collector does not mess with it. The AddrOfPinnedObject call returns an IntPtr pointing to the start of the array and the Marshal.PtrToStructure does the work of marshaling the byte[] to the structure.
If the original structure data did not start at the outset of the byte array you would use the following assuming the structure data starts at position 11 of the array:
Message p = (Message)Marshal.PtrToStructure( Marshal.UnsafeAddrOfPinnedArrayElement(pinnedPacket, 11), typeof(Message));