Reputation: 1244
So I have made a struct that I want to send around using a simple DatagramSocket.
The struct code is as follows:
public struct MsgData
{
private readonly int _value;
private readonly string _descr;
public MsgData(string desc, int value)
{
_descr = desc;
_value = value;
}
public int GetValue()
{
return _value;
}
public string GetDescr()
{
return _descr;
}
}
I proceed by converting to a byte array like so:
public static byte[] GetBytes(MsgData message)
{
var size = Marshal.SizeOf(message);
var data = new byte[size];
System.IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(message, ptr, true);
Marshal.Copy(ptr, data, 0, size);
Marshal.FreeHGlobal(ptr);
return data;
}
and return it to a MsgData struct like so:
public static MsgData GetMessage(byte[] bytes)
{
var data = new MsgData();
var size = Marshal.SizeOf(data);
var ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(bytes, 0, ptr, size);
data = Marshal.PtrToStructure<MsgData>(ptr);
Marshal.FreeHGlobal(ptr);
return data;
}
However I get a:
System.ArgumentOutOfRangeException: 'Requested range extends past the end of the array.'
when trying to convert on line:
Marshal.Copy(bytes, 0, ptr, size);
I'm going to go with a simple Serialization instad now but I wonder why this doesn't work as expected?
Upvotes: 1
Views: 1981
Reputation: 391326
There are several problems with your code so let me explain what you need to do in order to fix it.
First, your structure has a layout that is optimized for quick memory access. When marshalling this into a byte array you're going to, by default, copy that memory layout.
Your Marshal.SizeOf(...)
call reflects this. It returns 16, always. How can that be? How can your string be marshalled to something inside these 16 bytes, even if the string is much longer than 16?
The answer is that it doesn't. Instead you're marshalling the pointer to the string object as bytes.
16 bytes is 8 bytes for the int (4 for the int + 4 for padding to align the next value on a 8-byte memory address boundary, I'm running 64-bit), and then 8 bytes for the string reference (address).
So what needs to be done? You need to adorn your structure with a few attributes to tell the marshalling engine how to cope with it:
[StructLayout(LayoutKind.Sequential, Pack=0)]
public struct MsgData
{
[MarshalAs(UnmanagedType.I4)]
private readonly int _value;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)]
private readonly string _descr;
... rest as you have it
After doing this, you will get this size from Marshal.SizeOf(...)
: 68.
68 = 4 bytes for the int + 64 for the string.
Note that when marshalling, dynamically sized results aren't really that easy to handle so setting an upper limit on the string is the way to go, for now.
However, there is a much easier solution to your problem, use the built in binary serialization in .NET.
Here are two new versions of your Get*
methods that doesn't require you to do marshalling:
public static byte[] GetBytes(MsgData message)
{
using (var stream = new MemoryStream())
{
new BinaryFormatter().Serialize(stream, message);
return stream.ToArray();
}
}
public static MsgData GetMessage(byte[] bytes)
{
using (var stream = new MemoryStream(bytes))
{
return (MsgData)new BinaryFormatter().Deserialize(stream);
}
}
Note that you will need to apply the SerializableAttribute
to your struct:
[Serializable]
public struct MsgData
{
Upvotes: 2