So I am getting a variable number of coordinates (latitude and longitude). I want to pack these coordinates in a straight forward way that can be easily sent via UDP and unpacked. How can I go about this with C#?
I'm assuming I declare a struct and then use the built in marshalling to get a byte array to send. How does one do this when there is a variable number of points involved?
Most of my programming experience has been in Python, but I need this to be done in C#, which I have limited experience with.
edit: I'll add some code I was testing out, since I feel like no one responds to just text.
namespace ConsoleApplication1
{
class Testing
{
static void Main(string[] args)
{
// Console.WriteLine("Starting");
// string text = "Hello";
// byte[] data = Encoding.ASCII.GetBytes(text);
StartPacket test = new StartPacket();
test.len = 3;
List<double> points = new List<double>();
points.Add(3.14);
points.Add(5);
points.Add(-1023.1231311);
test.points = points;
byte[] data = StructureToByteArray(test);
SendUdp(65456, "192.168.20.100", data);
}
static void SendUdp(int srcPort, string dstIp, byte[] data)
{
Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,
ProtocolType.Udp);
IPAddress dst = IPAddress.Parse(dstIp);
IPEndPoint endPoint = new IPEndPoint(dst, srcPort);
sock.SendTo(data, endPoint);
}
public struct StartPacket
{
public uint len;
public List<double> points;
}
public static byte[] StructureToByteArray(object obj)
{
int len = Marshal.SizeOf(obj);
byte[] arr = new byte[len];
IntPtr ptr = Marshal.AllocHGlobal(len);
Marshal.StructureToPtr(obj, ptr, true);
Marshal.Copy(ptr, arr, 0, len);
Marshal.FreeHGlobal(ptr);
return arr;
}
}
}
This code fails with:
Type 'ConsoleApplication1.Testing+StartPacket' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.
This seems more like a serialization/deserialization task. The simplest approach is to mark the class with Serializable then use the BinaryFormatter like this:
public class Program
{
public static void Main(string[] args)
{
var startPacket = new StartPacket();
startPacket.len = 3;
startPacket.points = new List<double>() { 3.14, 5, -1023.1231311 };
// serialize into a byte array for Socket.SendTo()
var formatter = new BinaryFormatter();
var ms = new MemoryStream();
formatter.Serialize(ms, startPacket);
var bytes = ms.ToArray();
// assuming we received bytes[] from a socket, deserialize into StartPacket object
ms = new MemoryStream(bytes);
formatter = new BinaryFormatter();
startPacket = (StartPacket)formatter.Deserialize(ms);
}
}
[Serializable()]
public struct StartPacket
{
public uint len;
public List<double> points;
}
However, this is not very efficient memory-wise--this example generates a 524-byte serialized StartPacket, probably due to the fact that the List<> will have a capacity of more than 3 doubles. Making points an array with the specific size only gets us down to 211 bytes. Without looking into it, I'd guess that there's a lot of overhead in the serialized version of the struct (like the names and types of variables, etc). However, if you're not too concerned about the size of the packet, then this might work for you.
If you want something more efficient, then you can add methods to StartPacket like so:
public class Program
{
public static void Main(string[] args)
{
var startPacket = new StartPacket();
startPacket.len = 3;
startPacket.points = new List<double> { 3.14, 5, -1023.1231311 };
// create an array to send through the socket
var arr = startPacket.ToArray();
// create a StartPacket from an array we received from a socket
var newStartPacket = StartPacket.FromArray(arr);
}
}
public struct StartPacket
{
public uint len;
public List<double> points;
public byte[] ToArray()
{
var arr = BitConverter.GetBytes(len);
foreach (var point in points)
{
arr = arr.Concat(BitConverter.GetBytes(point)).ToArray();
}
return arr;
}
public static StartPacket FromArray(byte[] array)
{
var sp = new StartPacket();
sp.len = BitConverter.ToUInt32(array, 0);
sp.points = new List<double>();
for (int i = 0; i < sp.len; i++)
{
sp.points.Add(BitConverter.ToDouble(array, 4 + i * 8));
}
return sp;
}
}
Note that neither of these account for the endianness of the system. Hope this helps.
Related
Is it possible to Marshal an struct array of dynamic size like so? I'm trying to send data over a network and instead of, in this example, sending 5 different packets containing one testStruct each (which I'm able to do just fine and it works) I would like to group those packets for efficiency into one packet of sendTestStruct containing an array of 5 testStructs.
public struct sendTestStruct
{
public int numberOfStructs;
public testStruct[] structs;
}
public struct testStruct
{
public float float1, float2;
public int int1, int2;
public byte byte1, byte2;
}
sendTestStruct send = new();
send.structs = new testStruct[5];
send.numberOfStructs = 5;
for (var i = 0; i < 5; i++)
{
testStruct numbers = new();
numbers.float1 = Random.Range(0f, 10f);
numbers.float2 = Random.Range(0f, 10f);
numbers.int1 = Random.Range(0, 10);
numbers.int2 = Random.Range(0, 10);
numbers.byte1 = (byte)Random.Range(0, 10);
numbers.byte2 = (byte)Random.Range(0, 10);
send.structs[i] = numbers;
}
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(send));
Marshal.StructureToPtr(send, ptr, false);
sendTestStruct newstruct = Marshal.PtrToStructure<sendTestStruct>(ptr);
Marshal.FreeHGlobal(ptr);
But I get an error
Structure field of type testStruct[] can't be marshalled as LPArray
Is there a way to do this?
The error happens when trying to do
sendTestStruct newstruct = Marshal.PtrToStructure(ptr);
How do I convert a structure that contains an array to a byte array in C#?
There was a question here about a struct without array.
But if the struct contains an array like this:
public struct DiObject
{
public byte Command;
public byte ErrorClass;
public byte Reserved;
public byte Flags;
}
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
It results with an access violation exception when converting the struct in a byte:
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
My goal is to send a message in bytes in a message queue with MSMQ.
Here the complete code that compiles and reproduce the problem.
using System;
//using System.IO;
//using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
public struct DiObject
{
public byte Command;
public byte ErrorClass;
public byte Reserved;
public byte Flags;
}
// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
internal class Program
{
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
private static MyPacket FromBytes(byte[] data)
{
var packet = new MyPacket();
var dataSize = Marshal.SizeOf(packet);
var ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
packet = (MyPacket) Marshal.PtrToStructure(ptr, packet.GetType());
Marshal.FreeHGlobal(ptr);
return packet;
}
private static void Main(string[] args)
{
const string queuePath = #".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 2;
// 8 bytes
packet.Di = new DiObject[packet.NumDi];
packet.Di[0].Command = 2;
packet.Di[0].ErrorClass = 3;
packet.Di[0].Flags = 4;
packet.Di[0].Reserved = 5;
packet.Di[1].Command = 6;
packet.Di[1].ErrorClass = 7;
packet.Di[1].Flags = 8;
packet.Di[1].Reserved = 9;
// Convert the struct in bytes
const int packetSize = 16;
var packetBytes = GetBytes(packet, packetSize);
// Create the message
/*
var msg = new Message();
msg.BodyStream = new MemoryStream(packetBytes);
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};
// Send the message to the queue
q.Send(msg);
*/
}
}
}
The problem lies with wrong assumption about how structure is represented in C#
// 8 + (numDi*4) bytes
[Serializable]
public struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
}
The assumption that size of public DiObject[] Di member is numDi * 4 is not true. In place of this field there is a pointer to the array of structures. Array is a class in .NET and is not included in place in structure declaration.
To solve this problem one can use fixed arrays. I understand that the idea behind the design is to get variable length array and it is presented in next code listing.
This code does not raise AccessViolationException during executin:
using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DiObject
{
[FieldOffset(0)]
public byte Command;
[FieldOffset(1)]
public byte ErrorClass;
[FieldOffset(2)]
public byte Reserved;
[FieldOffset(3)]
public byte Flags;
}
// 8 + (numDi*4) bytes
[Serializable]
public unsafe struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public fixed byte Di[2 * 4];
}
internal unsafe class Program
{
private static byte[] GetBytes(MyPacket packet, int packetSize)
{
var data = new byte[packetSize];
var ptr = Marshal.AllocHGlobal(packetSize);
// ==== Access violation exception occurs here ====
Marshal.StructureToPtr(packet, ptr, true);
Marshal.Copy(ptr, data, 0, packetSize);
Marshal.FreeHGlobal(ptr);
return data;
}
private static MyPacket FromBytes(byte[] data)
{
var packet = new MyPacket();
var dataSize = Marshal.SizeOf(packet);
var ptr = Marshal.AllocHGlobal(dataSize);
Marshal.Copy(data, 0, ptr, dataSize);
packet = (MyPacket)Marshal.PtrToStructure(ptr, packet.GetType());
Marshal.FreeHGlobal(ptr);
return packet;
}
private static void Main(string[] args)
{
const string queuePath = #".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 2;
// 8 bytes
// packet.Di = new DiObject[packet.NumDi];
packet.Di[0] = 2;
packet.Di[1] = 3;
packet.Di[2] = 4;
packet.Di[3] = 5;
packet.Di[4] = 6;
packet.Di[5] = 7;
packet.Di[6] = 8;
packet.Di[7] = 9;
// Convert the struct in bytes
int packetSize = Marshal.SizeOf<MyPacket>();
var packetBytes = GetBytes(packet, packetSize);
// Create the message
var msg = new Message();
msg.BodyStream = new MemoryStream(packetBytes);
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath); // {Formatter = new BinaryMessageFormatter()};
// Send the message to the queue
q.Send(msg);
}
}
}
Code below provides efficient conversion to byte array and from byte array for MyPacket struct with variable internal array size. Implementation avoids casts and bounds checks by using unsafe pointer arithmetic.
using System;
using System.IO;
using System.Messaging;
using System.Runtime.InteropServices;
namespace StructToBytes
{
// 4 bytes
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct DiObject
{
[FieldOffset(0)]
public byte Command;
[FieldOffset(1)]
public byte ErrorClass;
[FieldOffset(2)]
public byte Reserved;
[FieldOffset(3)]
public byte Flags;
}
[Serializable]
public unsafe struct MyPacket
{
public uint ProtocolIdentifier;
public uint NumDi;
public DiObject[] Di;
public byte[] ToBytes()
{
byte[] buffer = new byte[NumDi];
fixed(DiObject* pDi = Di)
fixed(byte* pBuff = buffer)
{
var pBuffDi = (DiObject*)pBuff;
var pDiPtr = pDi;
for (int i = 0; i < NumDi; i++)
*pBuffDi++ = *pDiPtr++;
}
return buffer;
}
public static MyPacket Create(byte[] buffer)
{
// argument checking code here
var packet = new MyPacket();
packet.ProtocolIdentifier = buffer[0];
packet.NumDi = buffer[1];
packet.Di = new DiObject[packet.NumDi];
fixed (byte* pBuf = buffer)
fixed (DiObject* pDi = packet.Di)
{
byte* pBufPtr = pBuf;
pBufPtr += 2;
var pBufDi = (DiObject*)pBufPtr;
var pDiPtr = pDi;
for (int i = 0; i < packet.NumDi; i++)
*pDiPtr++ = *pBufDi++;
}
return packet;
}
}
internal unsafe class Program
{
private static void Main(string[] args)
{
const string queuePath = #".\private$\test_msmq";
// Create the packet
var packet = new MyPacket();
// 8 bytes
packet.ProtocolIdentifier = 1;
packet.NumDi = 5;
// 8 bytes
packet.Di = new DiObject[packet.NumDi];
packet.Di[0].Command = 2;
packet.Di[0].ErrorClass = 3;
packet.Di[0].Flags = 4;
packet.Di[0].Reserved = 5;
packet.Di[1].Command = 6;
packet.Di[1].ErrorClass = 7;
packet.Di[1].Flags = 8;
packet.Di[1].Reserved = 9;
packet.Di[2].Command = 6;
packet.Di[2].ErrorClass = 7;
packet.Di[2].Flags = 8;
packet.Di[2].Reserved = 9;
packet.Di[3].Command = 6;
packet.Di[3].ErrorClass = 7;
packet.Di[3].Flags = 8;
packet.Di[3].Reserved = 9;
// Create the message
var msg = new Message();
msg.BodyStream = new MemoryStream(packet.ToBytes());
// Open or create the message queue
if (!MessageQueue.Exists(queuePath))
MessageQueue.Create(queuePath);
// Open the queue
var q = new MessageQueue(queuePath);
// Send the message to the queue
q.Send(msg);
}
}
}
Is my method "ToByte()" the correct way to convert my data into
bytes, in order to be sent via Udp?
How can I do the reverse? (Watch the "received packet constructor")
Should I worry about the integrity of the received packet in order to
avoid exceptions?
public class GameDataPacket
{
private MultiplayerService.Command command_;
private string nickname_;
private float x_;
private float y_;
public GameDataPacket()
{
this.command_ = MultiplayerService.Command.Nothing;
this.nickname_ = "noname";
this.x_ = 0.0f;
this.y_ = 0.0f;
}
// Constructor for received packet
public GameDataPacket(byte[] data)
{
this.command_ = (MultiplayerService.Command)BitConverter.ToInt32(data, 0);
// How to get those values from the RECEIVED packet?
this.nickname_ = Encoding.UTF8.GetString(data, ??, ??);
this.x_ = BitConverter.ToSingle(data, ??);
this.y_ = BitConverter.ToSingle(data, ??);
}
How I transform into bytes my informations:
public byte[] ToByte()
{
// FORMAT
// |Command|Name|x|y|
List<byte> result = new List<byte>();
result.AddRange(BitConverter.GetBytes((int)command_));
result.AddRange(Encoding.UTF8.GetBytes(nickname_));
result.AddRange(BitConverter.GetBytes(x_));
result.AddRange(BitConverter.GetBytes(y_));
return result.ToArray();
}
Because the string could be any length you'll need to either send the length of the name, or add something to the packet that lets you detect the end of the string. Prefixing the length is probably the easiest and most straight forward.
Updated Constructor and To-bytes Method:
// Constructor for received packet
public GameDataPacket(byte[] data)
{
int packetOffset = 0;
this.command_ = (MultiplayerService.Command)BitConverter.ToInt32(data, packetOffset);
packetOffset += 4;
// read the length (in bytes) of the nickname
int nickDataLength = BitConverter.ToInt32(data, packetOffset);
packetOffset += 4;
// read the nick name
this.nickname_ = Encoding.UTF8.GetString(data, packetOffset, nickDataLength);
packetOffset += nickDataLength;
this.x_ = BitConverter.ToSingle(data, packetOffset);
packetOffset += 4;
this.y_ = BitConverter.ToSingle(data, packetOffset);
}
public byte[] ToByte()
{
// FORMAT
// |Command|NameDataLength|Name|x|y|
List<byte> result = new List<byte>();
result.AddRange(BitConverter.GetBytes((int)command_));
byte[] nicknameBytes = Encoding.UTF8.GetBytes(nickname_);
result.AddRange(BitConverter.GetBytes(nicknameBytes.Length));
result.AddRange(nicknameBytes);
result.AddRange(BitConverter.GetBytes(x_));
result.AddRange(BitConverter.GetBytes(y_));
return result.ToArray();
}
You could use BinaryFormatter...
[Serializable]
public class MyClass
{
public int MyProperty { get; set; }
}
// On the sending side...
var myClass = new MyClass { MyProperty = 1 };
byte[] bytes = null;
using (var ms = new MemoryStream())
{
var bf = new BinaryFormatter();
bf.Serialize(ms, myClass);
bytes = ms.ToArray();
}
// On the receiving side...
MyClass other = null;
using (var ms = new MemoryStream(bytes))
{
var bf = new BinaryFormatter();
other = (MyClass)bf.Deserialize(ms);
}
I would like to save a Color[] to a file. To do so, I found that saving a byte array to a file using "System.IO.File.WriteAllBytes" should be very efficient.
I would like to cast my Color[] (array of struct) to a byte array into a safe way considering:
Potential problem of little endian / big endian (I think it is impossible to happen but want to be sure)
Having 2 differents pointer to the same memory which point to different type. Does the garbage collection will know what to do - moving objects - deleting a pointer ???
If it is possible, it would be nice to have a generic way to cast array of byte to array of any struct (T struct) and vice-versa.
If not possible, why ?
Thanks,
Eric
I think that those 2 solutions make a copy that I would like to avoid and also they both uses Marshal.PtrToStructure which is specific to structure and not to array of structure:
Reading a C/C++ data structure in C# from a byte array
How to convert a structure to a byte array in C#?
Since .NET Core 2.1, yes we can! Enter MemoryMarshal.
We will treat our Color[] as a ReadOnlySpan<Color>. We reinterpret that as a ReadOnlySpan<byte>. Finally, since WriteAllBytes has no span-based overload, we use a FileStream to write the span to disk.
var byteSpan = MemoryMarshal.AsBytes(colorArray.AsSpan());
fileStream.Write(byteSpan);
As an interesting side note, you can also experiment with the [StructLayout(LayoutKind.Explicit)] as an attribute on your fields. It allows you to specify overlapping fields, effectively allowing the concept of a union.
Here is a blog post on MSDN that illustrates this. It shows the following code:
[StructLayout(LayoutKind.Explicit)]
public struct MyUnion
{
[FieldOffset(0)]
public UInt16 myInt;
[FieldOffset(0)]
public Byte byte1;
[FieldOffset(1)]
public Byte byte2;
}
In this example, the UInt16 field overlaps with the two Byte fields.
This seems to be strongly related to what you are trying to do. It gets you very close, except for the part of writing all the bytes (especially of multiple Color objects) efficiently. :)
Regarding Array Type Conversion
C# as a language intentionally makes the process of flattening objects or arrays into byte arrays difficult because this approach goes against the principals of .NET strong typing. The conventional alternatives include several serialization tools which are generally seen a safer and more robust, or manual serialization coding such as BinaryWriter.
Having two variables of different types point to the same object in memory can only be performed if the types of the variables can be cast, implicitly or explicitly. Casting from an array of one element type to another is no trivial task: it would have to convert the internal members that keep track of things such as array length, etc.
A simple way to write and read Color[] to file
void WriteColorsToFile(string path, Color[] colors)
{
BinaryWriter writer = new BinaryWriter(File.OpenWrite(path));
writer.Write(colors.Length);
foreach(Color color in colors)
{
writer.Write(color.ToArgb());
}
writer.Close();
}
Color[] ReadColorsFromFile(string path)
{
BinaryReader reader = new BinaryReader(File.OpenRead(path));
int length = reader.ReadInt32();
Colors[] result = new Colors[length];
for(int n=0; n<length; n++)
{
result[n] = Color.FromArgb(reader.ReadInt32());
}
reader.Close();
}
You could use pointers if you really want to copy each byte and not have a copy but the same object, similar to this:
var structPtr = (byte*)&yourStruct;
var size = sizeof(YourType);
var memory = new byte[size];
fixed(byte* memoryPtr = memory)
{
for(int i = 0; i < size; i++)
{
*(memoryPtr + i) = *structPtr++;
}
}
File.WriteAllBytes(path, memory);
I just tested this and after adding the fixed block and some minor corrections it looks like it is working correctly.
This is what I used to test it:
public static void Main(string[] args)
{
var a = new s { i = 1, j = 2 };
var sPtr = (byte*)&a;
var size = sizeof(s);
var mem = new byte[size];
fixed (byte* memPtr = mem)
{
for (int i = 0; i < size; i++)
{
*(memPtr + i) = *sPtr++;
}
}
File.WriteAllBytes("A:\\file.txt", mem);
}
struct s
{
internal int i;
internal int j;
}
The result is the following:
(I manually resolved the hex bytes in the second line, only the first line was produced by the program)
public struct MyX
{
public int IntValue;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U1)]
public byte[] Array;
MyX(int i, int b)
{
IntValue = b;
Array = new byte[3];
}
public MyX ToStruct(byte []ar)
{
byte[] data = ar;//= { 1, 0, 0, 0, 9, 8, 7 }; // IntValue = 1, Array = {9,8,7}
IntPtr ptPoit = Marshal.AllocHGlobal(data.Length);
Marshal.Copy(data, 0, ptPoit, data.Length);
MyX x = (MyX)Marshal.PtrToStructure(ptPoit, typeof(MyX));
Marshal.FreeHGlobal(ptPoit);
return x;
}
public byte[] ToBytes()
{
Byte[] bytes = new Byte[Marshal.SizeOf(typeof(MyX))];
GCHandle pinStructure = GCHandle.Alloc(this, GCHandleType.Pinned);
try
{
Marshal.Copy(pinStructure.AddrOfPinnedObject(), bytes, 0, bytes.Length);
return bytes;
}
finally
{
pinStructure.Free();
}
}
}
void function()
{
byte[] data = { 1, 0, 0, 0, 9, 8, 7 }; // IntValue = 1, Array = {9,8,7}
IntPtr ptPoit = Marshal.AllocHGlobal(data.Length);
Marshal.Copy(data, 0, ptPoit, data.Length);
var x = (MyX)Marshal.PtrToStructure(ptPoit, typeof(MyX));
Marshal.FreeHGlobal(ptPoit);
var MYstruc = x.ToStruct(data);
Console.WriteLine("x.IntValue = {0}", x.IntValue);
Console.WriteLine("x.Array = ({0}, {1}, {2})", x.Array[0], x.Array[1], x.Array[2]);
}
Working code for reference (take care, I did not need the alpha channel in my case):
// ************************************************************************
// If someday Microsoft make Color serializable ...
//public static void SaveColors(Color[] colors, string path)
//{
// BinaryFormatter bf = new BinaryFormatter();
// MemoryStream ms = new MemoryStream();
// bf.Serialize(ms, colors);
// byte[] bytes = ms.ToArray();
// File.WriteAllBytes(path, bytes);
//}
// If someday Microsoft make Color serializable ...
//public static Colors[] LoadColors(string path)
//{
// Byte[] bytes = File.ReadAllBytes(path);
// BinaryFormatter bf = new BinaryFormatter();
// MemoryStream ms2 = new MemoryStream(bytes);
// return (Colors[])bf.Deserialize(ms2);
//}
// ******************************************************************
public static void SaveColorsToFile(Color[] colors, string path)
{
var formatter = new BinaryFormatter();
int count = colors.Length;
using (var stream = File.OpenWrite(path))
{
formatter.Serialize(stream, count);
for (int index = 0; index < count; index++)
{
formatter.Serialize(stream, colors[index].R);
formatter.Serialize(stream, colors[index].G);
formatter.Serialize(stream, colors[index].B);
}
}
}
// ******************************************************************
public static Color[] LoadColorsFromFile(string path)
{
var formatter = new BinaryFormatter();
Color[] colors;
using (var stream = File.OpenRead(path))
{
int count = (int)formatter.Deserialize(stream);
colors = new Color[count];
for (int index = 0; index < count; index++)
{
byte r = (byte)formatter.Deserialize(stream);
byte g = (byte)formatter.Deserialize(stream);
byte b = (byte)formatter.Deserialize(stream);
colors[index] = Color.FromRgb(r, g, b);
}
}
return colors;
}
// ******************************************************************
I have a System.Array of value struct types, something like this:
public value struct Position
{
int timestamp;
float x;
float y;
}
Position[] positions = new Position[1000 * 1000];
After I initialize the array with values, how can I get a byte[] of it's content, without serializing one item at a time?
In C++/CLI I would use a pin_ptr to get the pointer to the array content and I would copy the data from there. Can I do something like this in C#?
EDIT: I need to write the raw data to disk, as if it were a C struct, without any kind of serialization.
I tagged this question as C# for broader exposure, but actually I'm trying to serialize the data from IronPython, so this means I can't use any unsafe C# functionality.
Maybe this will help:
[Serializable()]
public struct Position
{
int timestamp;
float x;
float y;
}
static void Main(string[] args)
{
var positions = new Position[1000 * 1000];
GetBytes(positions);
}
private static byte[] GetBytes(object obj)
{
using (var memoryStream = new System.IO.MemoryStream())
{
var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
binaryFormatter.Serialize(memoryStream, obj);
return memoryStream.ToArray();
}
}
Here's a method that doesn't require unsafe code:
[Updated to remove the for loop and perform the copy in a single pass]
private static byte[] StructureToByteArray(Position[] posArray)
{
if (posArray == null || posArray.Length == 0)
{
return new byte[0];
}
var lenOfObject = Marshal.SizeOf(typeof(Position));
var len = lenOfObject * posArray.Length;
var arr = new byte[len];
var handle = GCHandle.Alloc(posArray, GCHandleType.Pinned);
try
{
var ptr = handle.AddrOfPinnedObject();
Marshal.Copy(ptr, arr, 0, len);
}
finally
{
handle.Free();
}
return arr;
}
I believe this is the equivalent to the C++/CLI pin_ptr using unsafe C# code:
public static unsafe byte[] GetBytes(Position[] positions)
{
byte[] result = new byte[positions.Length * Marshal.SizeOf(typeof(Position))];
fixed (Position* ptr = &positions[0])
{
Marshal.Copy((IntPtr)ptr, result, 0, result.Length);
}
return result;
}