I am writing a C# application that needs to communicate with a unmanaged C++ application over the network using pre-defined messages. Every message starts with a message id and length.
Byte 0 MessageId
Byte 1 Length
After that, it is different for each message. Such as a message to set the time, will be
Byte 0 MessageId
Byte 1 Length
Byte 2..5 Time
Starting out, I figured I would just create a base class which all other messages would inherit from, which would have a method to "serialize" it:
public class BaseMessage
{
public virtual byte MessageId { get; }
public virtual byte Length { get; }
public byte[] GetAsMessage()
{
...
}
}
public class SetTimeMessage : BaseMessage
{
public override byte MessageId => 1;
public override byte Length {get; private set; }
public byte[] Time {get; private set; }
public SetTimeMessage(byte[] time)
{
...
}
}
In the C++ code, if I create a SetTimeMessage
and want to send it over the network, a method like GetAsMessage
which is defined on the base class is called, which will simply copy the contents of the object into a buffer. This works for all derived types of BaseMessage
.
How can I do something similar in C#? I tried using the [Serializable]
attribute and the BinaryFormatter
, but it returned a huge byte array, not just the 6 bytes that the values actually contain.
I also looked into Marshalling
, but it only seems to work with structs? If so, it seems like a lot of work needs to be done for each message type, since structs don't support inheritance (there are many messages I need to implement).
I will also receive messages back in the same format, which need to be deserialized back to an object. Anybody know of a neat way of achieving what I'm after?
Edit
I've done some experimenting with structs and marshalling, after input from Spo1ler.
Doing it this way, It's simple to serialize any message to a byte array
public interface IMsg
{
byte MessageId { get; }
byte Length { get; }
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SetTime: IMsg
{
public byte Id { get; }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public byte[] Time;
public StartLoggerWithSampleInterval(byte time)
{
Id = (byte) MessageType.SetTime;
Time = time;
}
}
I then have this static class to serialize the messages:
public static class MessageSerializer
{
public static byte[] Serialize(IMsg msg)
{
int size = Marshal.SizeOf(msg);
byte[] serialized = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(msg, ptr, true);
Marshal.Copy(ptr, serialized, 0, size);
Marshal.FreeHGlobal(ptr);
return serialized;
}
}
Initially this seems to be working like I want. Any problems that can show up doing it this way?
However, the deserialization of the messages will be a pain to write, though the first byte in the message (MessageId) should tell me what type of struct the byte array translates to