Marshalling struct array of dynamic size? - c#

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);

Related

Passing a complex Struct (with inner array of struct) from C# to C++

I'm working on a driver for a scanner. Got dll and header files from provider, aside of a manual in PDF, written in native C++ (dont have source code). Need to use it within a C# project but I'm having problems with the structs (either trying to read or send them).
I got the dll methods using the Command Prompt, demangling them in a website (since it has +100). Of course, I wont use all of them, just the ones I need. Didn't have problems with the ones using primitive data types, in fact, made the scanner turn on/off, scan and such.
My main problem is the following: I need to set some parameters, because with the default ones I'm not getting the needed info (and that's the MOST important thing I need, actually). The only way to do it is with a method that include 2 params: the ID (just an int) and the setting (an struct). That struct has internally not one, but 2 different structs instances (and one of them is an array, within another kind of struct as one of the params). In other words, gonna need to work with 4 different structs.
I declared all my structs, following the template provided in the .h file, and inmported the method. When I try to do a test it keeps giving me an error. I believe the problem is the array of structs. I tried with a normal passing, Marshaling, using a pin to array, changing data type, adding a "MarshalAs" with all bool vars... nothing seems to work.
Been trying to solve this for days already. Dont know what am I doing wrong (because this is my first time importing methods). I read about the C++/Cli thing but never worked with that either.
See code below (I kinda modified a little because of confidentiality of the information)
This is how is defined in the .h file (C++)
// The structs (won't add all parameters, but are basically the same type)
typedef struct _ImParam
{
UINT Format;
UINT Resolution;
UINT ColorDepth;
} IM_PARAM;
typedef struct _sValues
{
UINT Xpos;
UINT Ypos;
UINT Width;
UINT Height;
BOOL Milli;
} S_VALUES;
typedef struct _sProperties
{
BOOL Enable;
S_VALUES Properties;
} S_PROPERTIES;
typedef struct _DevParam
{
BOOL Enable;
UINT Font;
char Symbol;
IM_PARAM Image1;
IM_PARAM Image2;
S_PROPERTIES Properties[10];
UINT FeedMode;
} DevParam;
// more code, comments, etc. etc.
// This is how is defined
BOOL SetParameters( DWORD ID, DevParams DParam )
This is how I build the structs in C#
[StructLayout(LayoutKind.Sequential)]
public struct ImParam
{
public uint Format;
public uint Resolution;
public uint ColorDepth;
public ImParam(uint n)
{
Format = n;
Resolution = 300;
ColorDepth = 256;
}
};
[StructLayout(LayoutKind.Sequential)]
public struct sValues
{
public uint Xpos;
public uint Ypos;
public uint Width;
public uint Height;
public bool Milli;
public sValues(uint n)
{
Xpos = n;
Ypos = n;
Width = n;
Height = n;
Milli = false;
}
};
[StructLayout(LayoutKind.Sequential)]
public struct sProperties
{
public bool Enable;
public sValues Properties;
public sProperties(int n)
{
Enable = false;
Front = false;
Properties = new sValues(n);
}
};
// Commented code is from another attemp
[StructLayout(LayoutKind.Sequential)]
public struct DevParam
{
public bool Enable;
public uint Font;
public char Symbol;
public ImParam Image1;
public ImParam Image2;
public IntPtr Properties;
//public sProperties[] Properties;
public uint FeedMode;
public DeviceParameters(IntPtr SnP) //(int n)
{
Enable = true;
Font = 0;
Symbol = '?';
Image1 = new ImParam(3);
Image2 = new ImParam(3);
Properties = SnP;
/*Properties = new sProperties[n];
*for(int i = 0; i < n; i++)
* Properties[i] = new sProperties(0);*/
FeedMode = 1;
}
};
// .dll file path definition, some methods imported, etc. etc.
[DllImport(path, EntryPoint = "?SetParameters##YGHKU_DevParam###Z")]
public static extern bool SetParameters(int ID, DevParam dParam);
And this is when I do call it (added commented code to show you my attemps)
static void Main(string[] args)
{
bool res = false;
int ID;
sProperties[] SnP = new sProperties[10];
for (int i = 0; i < 10; i++)
SnP[i] = new sProperties(0);
try
{
// Some code to turn on scanner, get ID value and such
/* Attemp1: Passing the struct normaly.
* Result: ArgumentException [HRESULT: 0x80070057 (E_INVALIDARG))]
* try
* {
* DevParam dParam = new DevParam(10);
* res = Class1.SetParameters(ID, dParam);
* Console.WriteLine(res);
* }
* catch (Exception e) { Console.WriteLine(e); }*/
/* Attemp2: Marshaling each element of the array.
* Result: The managed PInvoke signature doesnt mach the destination one
* int S = Marshal.SizeOf(typeof(sProperties));
* DevParam dParam = new DevParam(Marshal.AllocHGlobal(SnP.Length*S));
* IntPtr ptr = dParam.Properties;
* for (int i = 0; i < SnP.Length; i++)
* {
* Marshal.StructureToPtr(SnP[i], ptr, false);
* ptr += S;
* }
* try
* {
* res = Class1.SetDevParam(ID, dParam);
* Console.WriteLine(res);
* }
* finally { Marshal.FreeHGlobal(dParam.sProperties); }*/
/* Attemp3: Adding a Pin Pointer to struct
* Result: Exception (Object has no primitive data and it can't
* be transfered into bits blocks) */
GCHandle SpHandle = GCHandle.Alloc(SnP, GCHandleType.Pinned);
try
{
DevParam dParam = new DevParam(SpHandle.AddrOfPinnedObject());
res = Class1.SetParameters(ID, dParam);
Console.WriteLine(res);
}
catch (Exception e) { Console.WriteLine(e); }
finally { SpHandle.Free(); }
// More code for testing other methods and blahblahblah
}
catch (Exception e) { Console.WriteLine(e); }
Console.WriteLine("Finished");
Console.ReadKey();
}
What do I expect? Getting just a boolean result to see if method executed sucessfully (and of course, if true, scanner should have defined the new parameters)
What do I get? A bunch of exceptions.
Please, any help would be great. Thanks in advance.
PD: Sorry for that long post.
PD2: I'm quite rockie, so please try to explain it "for dummies"
Thanks Hans. Seems it worked!
Just modiffied the struct as suggested:
[StructLayout(LayoutKind.Sequential)]
public struct DevParam
{
public bool Enable;
public uint Font;
public char Symbol;
public ImParam Image1;
public ImParam Image2;
//public IntPtr Properties;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public sProperties[] Properties;
public uint FeedMode;
public DeviceParameters(int n) //(IntPtr SnP)
{
Enable = true;
Font = 0;
Symbol = '?';
Image1 = new ImParam(3);
Image2 = new ImParam(3);
//Properties = SnP;
Properties = new sProperties[n];
for(int i = 0; i < n; i++)
Properties[i] = new sProperties(0);
FeedMode = 1;
}
};
And used the "Attemp1" code.

How to convert a structure that contains an array to a byte array?

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);
}
}
}

Packing udp packet of arbitrary number of coordinates

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.

C# copy array of structs to array of doubles

[StructLayout(LayoutKind.Sequential)]
public struct Demo
{
double X;
double Y;
}
var data = new Demo[128];
FillWithMeaningfulValues(data);
double[] doubles;
Copy(data, out doubles); // ?
How do I copy the demo array into the doubles array without having to for(...) through each element? In C++, I would use memcpy, but in C# I did not find what I need in Marshal.Copy.
void MethodIDoNotWantToUse(Demo[] demo, out double[] doubles)
{
doubles = new double[demo.Length * 2];
for(int i = 0, j = 0; i < demo.Length; ++i)
{
doubles[j++] = demo[i].X;
doubles[j++] = demo[i].Y;
}
}
void MethodIWouldPreferToUse(Demo[] demo, out double[] doubles)
{
doubles = new double[demo.Length * 2];
memcopy(doubles, demo, demo.Length * 2 * sizeof(double));
}
You'll do something like this. Marshal.Copy do provides you what you need.
Demo[] array = new Demo[2];
array[0] = new Demo {X = 5.6, Y= 6.6};
array[1] = new Demo {X = 7.6, Y = 8.6};
GCHandle handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
IntPtr pointer = handle.AddrOfPinnedObject();
double[] copy = new double[array.Length*2];//This length may be calculated
Marshal.Copy(pointer, copy, 0, copy.Length);
}
finally
{
if (handle.IsAllocated)
handle.Free();
}
Since the struct is blittable, the array of the struct is blittable. Therefore you can pin the array of struct and copy into the double array with Marshal.Copy.
void CopyDemoArrayToDoubleArray(Demo[] demo, out double[] doubles)
{
doubles = new double[demo.Length * 2];
GCHandle gch = GCHandle.Alloc(demo, GCHandleType.Pinned);
try
{
IntPtr demoPtr = gch.AddrOfPinnedObject();
Marshal.Copy(demoPtr, doubles, 0, doubles.Length);
}
finally
{
gch.Free();
}
}
You might do well to benchmark this against the simpler for loop that you want to avoid. It is plausible that the for loop will perform perfectly adequately.
It's possible to write a generic method that can convert arrays of any compatible type (by "compatible" I mean "elements must be value types and the size of the elements must be compatible").
You can use P/Invoke to call the Windows API CopyMemory() method.
However, bear in mind that there may not be any performance advantage to doing it this way; you should perform careful timings to be sure.
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
public TOut[] ConvertArray<TIn, TOut>(TIn[] input) where TIn:struct where TOut:struct
{
if (input == null)
throw new ArgumentNullException("input");
int sizeTIn = Marshal.SizeOf(typeof(TIn));
int sizeTOut = Marshal.SizeOf(typeof(TOut));
int sizeBytes = input.Length*sizeTIn;
if ((sizeBytes % sizeTOut) != 0)
throw new ArgumentException("Size of input type is not compatible with size of output type.");
int sizeOut = sizeBytes/sizeTOut;
var output = new TOut[sizeOut];
GCHandle inHandle = GCHandle.Alloc(input, GCHandleType.Pinned);
GCHandle outHandle = GCHandle.Alloc(output, GCHandleType.Pinned);
try
{
IntPtr inPtr = inHandle.AddrOfPinnedObject();
IntPtr outPtr = outHandle.AddrOfPinnedObject();
CopyMemory(outPtr, inPtr, (uint)sizeBytes);
}
finally
{
outHandle.Free();
inHandle.Free();
}
return output;
}
For your example, you could call this like so:
Demo[] test = new Demo[10];
for (int i = 0; i < 10; ++i)
test[i] = new Demo {X = i, Y = i};
var result = ConvertArray<Demo, double>(test);
for (int i = 0; i < 20; ++i)
Console.WriteLine(result[i]);

C# - Cast a byte array to an array of struct and vice-versa (reverse)

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;
}
// ******************************************************************

Categories

Resources