I've been trying to figure out whether it is possible to marshal a part of a struct into an array of bytes without any errors. The reason for this is because the part not going to be marshalled has a variable sized array. This is the the code I'm using.
C#:
public struct Random {
[MarshalAs(UnmanagedType.I4)]
public int a;
[MarshalAs(UnmanagedType.I4)]
public int b;
[MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_RECORD)]
public Random1[] r;
}
public struct Random1 {
[MarshalAs(UnmanagedType.I4)]
public int c;
}
private void Form1_Load(object sender, EventArgs e) {
int size = Marshal.SizeOf(typeof(Random));
int s = 4;
Random r = new Random();
Random r1 = new Random();
r.a = 1;
r.b = 5;
r.r = new Random1[2];
r.r[0].c = 10;
r.r[1].c = 12;
IntPtr p = Marshal.AllocHGlobal(size);
try {
byte[] arr = new byte[size];
Marshal.StructureToPtr(r, p, false);
Marshal.Copy(p, arr, 0, 8);
Marshal.FreeHGlobal(p);
p = Marshal.AllocHGlobal(size);
Marshal.Copy(arr, 0, p, 8);
r1 = (Random) Marshal.PtrToStructure(p, typeof(Random));
Marshal.FreeHGlobal(p);
p = IntPtr.Zero;
Debug.WriteLine(r1.a);
Debug.WriteLine(r1.b);
Debug.WriteLine(r1.r[0].c);
Debug.WriteLine(r1.r[1].c);
} finally {
if (p != IntPtr.Zero) {
Marshal.FreeHGlobal(p);
}
}
}
When I try this code it gives me an ArgumentException in the StructureToPtr. What am I doing wrong or can this be done? If not I've read something about using IntPtr. Could someone tell me how to do this?
I've been thinking long and hard about this one, and after trying several (very hackish) options, I have arrived at the conclusion that what you are doing is not possible.
First of all, it isn't possible to directly marshal an array of variable length, because no meaningful size offset can be computed by the Marshal, and therefore the memory layout can not be determined.
If the struct wouldn't contain any non-blittable, non-primitive types, you could theoretically do something like this:
byte[] buffer = new byte[8];
Marshal.Copy(new IntPtr(&r), buffer, 0, 8);
IntegersOnlyStruct partialStruct;
fixed (byte* b = buffer)
partialStruct = *(IntegersOnlyStruct*) b;
And obtain the two integers contained in the struct that way. However, since your Random1 is a non-primitive, non-blittable type this approach won't get you very far either.
The IntPtr approach to keep a pointer to a memory location where your Random1 struct is kept in memory is an alternative that could possibly work, but I haven't gone in-depth on that, yet.
I think you may want to re-think the way you approach this, as it's not possible in its current form. If anybody can prove otherwise, I'd be happy to see how it can be done.
Related
I want to use my laptop to communicate with MES(Manufacturing Execution System).
And when I serialized the data (struct type), something happen.
The code below is what I have done:
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct DataPackage
{
public int a;
public ushort b;
public byte c;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5)] public string d;
}
class Program
{
static void Main(string[] args)
{
DataPackage pack1 = new DataPackage();
pack1.a = 0x33333301;
pack1.b = 200;
pack1.c = 21;
pack1.d = "hello";
byte[] pack1_serialized = getBytes(pack1);
Console.WriteLine(BitConverter.ToString(pack1_serialized));
byte[] getBytes(DataPackage str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
IntPtr ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(str, ptr, true);
Marshal.Copy(ptr, arr, 0, size);
Marshal.FreeHGlobal(ptr);
return arr;
}
}
}
And here is the outcome:
I want the outcome to be like this:
33-33-33-01-00-C8-15-68-65-6C-6C-6F
So the questions are:
Why is the uint / ushort type data reverse after Marshalling?
Is there any other way that I can send the data in the sequence that I want ?
Why is the last word "o" in string "hello" disappear in the byte array ?
Thanks.
1 - Because your expected outcome is big endian, and your system appears to use little endian, so basically reversed order of bytes compared to what you expect.
2- Easiest way is to "convert" your numbers to big endian before marshalling (that is change them in a way which will produce desired result while converting them using little endian), for example like this:
static int ToBigEndianInt(int x) {
if (!BitConverter.IsLittleEndian)
return x; // already fine
var ar = BitConverter.GetBytes(x);
Array.Reverse(ar);
return BitConverter.ToInt32(ar, 0);
}
static ushort ToBigEndianShort(ushort x) {
if (!BitConverter.IsLittleEndian)
return x; // already fine
var ar = BitConverter.GetBytes(x);
Array.Reverse(ar);
return BitConverter.ToUInt16(ar, 0);
}
And then:
pack1.a = ToBigEndianInt(0x33333301);
pack1.b = ToBigEndianShort(200);
Note that this way of conversion is not very efficient and if you need more perfomance you can do this with some bit manipulations.
3 - Because string is null terminated, and this null terminator counts in SizeConst. Since you have it 5, there will be 4 characters of your string + 1 null terminator. Just increase SizeConst = 6 (that might add additional zeroes at the end because of Pack = 4).
[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]);
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'm working in C# with a Borland C API that uses a lot of byte pointers for strings. I've been faced with the need to pass some C# strings as (short lived) byte*.
It would be my natural assumption that a const object would not be allocated on the heap, but would be stored directly in program memory, but I've been unable to verify this in any documentation.
Here's an example of what I've done in order to generate a pointer to a constant string. This does work as intended in testing, I'm just not sure if it's really safe, or it's only working via luck.
private const string pinnedStringGetWeight = "getWeight";
unsafe public static byte* ExampleReturnWeightPtr(int serial)
{
fixed (byte* pGetWeight = ASCIIEncoding.ASCII.GetBytes(pinnedStringGetWeight))
return pGetWeight;
}
Is this const really pinned, or is there a chance it could be moved?
#Kragen:
Here is the import:
[DllImport("sidekick.dll", CallingConvention = CallingConvention.Winapi)]
public static extern int getValueByFunctionFromObject(int serial, int function, byte* debugCallString);
This is the actual function. Yes, it actually requires a static function pointer:
private const int FUNC_GetWeight = 0x004243D0;
private const string pinnedStringGetWeight = "getWeight";
unsafe public static int getWeight(int serial)
{
fixed (byte* pGetWeight = ASCIIEncoding.ASCII.GetBytes(pinnedStringGetWeight))
return Core.getValueByFunctionFromObject(serial, FUNC_GetWeight, pGetWeight);
}
Following is another method that I used when mocking my API, using a static struct, which I also hoped was pinned. I was hoping to find a way to simplify this.
public byte* getObjVarString(int serial, byte* varName)
{
string varname = StringPointerUtils.GetAsciiString(varName);
string value = MockObjVarAttachments.GetString(serial, varname);
if (value == null)
return null;
return bytePtrFactory.MakePointerToTempString(value);
}
static UnsafeBytePointerFactoryStruct bytePtrFactory = new UnsafeBytePointerFactoryStruct();
private unsafe struct UnsafeBytePointerFactoryStruct
{
fixed byte _InvalidScriptClass[255];
fixed byte _ItemNotFound[255];
fixed byte _MiscBuffer[255];
public byte* InvalidScriptClass
{
get
{
fixed (byte* p = _InvalidScriptClass)
{
CopyNullString(p, "Failed to get script class");
return p;
}
}
}
public byte* ItemNotFound
{
get
{
fixed (byte* p = _ItemNotFound)
{
CopyNullString(p, "Item not found");
return p;
}
}
}
public byte* MakePointerToTempString(string text)
{
fixed (byte* p = _ItemNotFound)
{
CopyNullString(p, text);
return p;
}
}
private static void CopyNullString(byte* ptrDest, string text)
{
byte[] textBytes = ASCIIEncoding.ASCII.GetBytes(text);
fixed (byte* p = textBytes)
{
int i = 0;
while (*(p + i) != 0 && i < 254 && i < textBytes.Length)
{
*(ptrDest + i) = *(p + i);
i++;
}
*(ptrDest + i) = 0;
}
}
}
How constants are allocated shouldn't matter in this case, because ASCIIEncoding.ASCII.GetBytes() returns a new byte array (it cannot return the constant's internal array, since it is encoded differently (edit: there is hopefully no way to get a pointer to a string's internal array, since strings are immutable)). However, the guarantee that the GC won't touch the array only lasts as long as the fixed scope does - in other words, when the function returns, the memory is no longer pinned.
Based on Kragans comment, I looked into the proper way to marshall my string to a byte pointer and am now using the following for the first example I used in my question:
[DllImport("sidekick.dll", CallingConvention = CallingConvention.Winapi)]
public static extern int getValueByFunctionFromObject(int serial, int function, [MarshalAs(UnmanagedType.LPStr)]string debugCallString);
Is there a way of mapping data collected on a stream or array to a data structure or vice-versa?
In C++ this would simply be a matter of casting a pointer to the stream as a data type I want to use (or vice-versa for the reverse)
eg: in C++
Mystruct * pMyStrct = (Mystruct*)&SomeDataStream;
pMyStrct->Item1 = 25;
int iReadData = pMyStrct->Item2;
obviously the C++ way is pretty unsafe unless you are sure of the quality of the stream data when reading incoming data, but for outgoing data is super quick and easy.
Most people use .NET serialization (there is faster binary and slower XML formatter, they both depend on reflection and are version tolerant to certain degree)
However, if you want the fastest (unsafe) way - why not:
Writing:
YourStruct o = new YourStruct();
byte[] buffer = new byte[Marshal.SizeOf(typeof(YourStruct))];
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
Marshal.StructureToPtr(o, handle.AddrOfPinnedObject(), false);
handle.Free();
Reading:
handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
o = (YourStruct)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(YourStruct));
handle.Free();
In case lubos hasko's answer was not unsafe enough, there is also the really unsafe way, using
pointers in C#. Here's some tips and pitfalls I've run into:
using System;
using System.Runtime.InteropServices;
using System.IO;
using System.Diagnostics;
// Use LayoutKind.Sequential to prevent the CLR from reordering your fields.
[StructLayout(LayoutKind.Sequential)]
unsafe struct MeshDesc
{
public byte NameLen;
// Here fixed means store the array by value, like in C,
// though C# exposes access to Name as a char*.
// fixed also requires 'unsafe' on the struct definition.
public fixed char Name[16];
// You can include other structs like in C as well.
public Matrix Transform;
public uint VertexCount;
// But not both, you can't store an array of structs.
//public fixed Vector Vertices[512];
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct Matrix
{
public fixed float M[16];
}
// This is how you do unions
[StructLayout(LayoutKind.Explicit)]
unsafe struct Vector
{
[FieldOffset(0)]
public fixed float Items[16];
[FieldOffset(0)]
public float X;
[FieldOffset(4)]
public float Y;
[FieldOffset(8)]
public float Z;
}
class Program
{
unsafe static void Main(string[] args)
{
var mesh = new MeshDesc();
var buffer = new byte[Marshal.SizeOf(mesh)];
// Set where NameLen will be read from.
buffer[0] = 12;
// Use Buffer.BlockCopy to raw copy data across arrays of primitives.
// Note we copy to offset 2 here: char's have alignment of 2, so there is
// a padding byte after NameLen: just like in C.
Buffer.BlockCopy("Hello!".ToCharArray(), 0, buffer, 2, 12);
// Copy data to struct
Read(buffer, out mesh);
// Print the Name we wrote above:
var name = new char[mesh.NameLen];
// Use Marsal.Copy to copy between arrays and pointers to arrays.
unsafe { Marshal.Copy((IntPtr)mesh.Name, name, 0, mesh.NameLen); }
// Note you can also use the String.String(char*) overloads
Console.WriteLine("Name: " + new string(name));
// If Erik Myers likes it...
mesh.VertexCount = 4711;
// Copy data from struct:
// MeshDesc is a struct, and is on the stack, so it's
// memory is effectively pinned by the stack pointer.
// This means '&' is sufficient to get a pointer.
Write(&mesh, buffer);
// Watch for alignment again, and note you have endianess to worry about...
int vc = buffer[100] | (buffer[101] << 8) | (buffer[102] << 16) | (buffer[103] << 24);
Console.WriteLine("VertexCount = " + vc);
}
unsafe static void Write(MeshDesc* pMesh, byte[] buffer)
{
// But byte[] is on the heap, and therefore needs
// to be flagged as pinned so the GC won't try to move it
// from under you - this can be done most efficiently with
// 'fixed', but can also be done with GCHandleType.Pinned.
fixed (byte* pBuffer = buffer)
*(MeshDesc*)pBuffer = *pMesh;
}
unsafe static void Read(byte[] buffer, out MeshDesc mesh)
{
fixed (byte* pBuffer = buffer)
mesh = *(MeshDesc*)pBuffer;
}
}
if its .net on both sides:
think you should use binary serialization and send the byte[] result.
trusting your struct to be fully blittable can be trouble.
you will pay in some overhead (both cpu and network) but will be safe.
If you need to populate each member variable by hand you can generalize it a bit as far as the primitives are concerned by using FormatterServices to retrieve in order the list of variable types associated with an object. I've had to do this in a project where I had a lot of different message types coming off the stream and I definitely didn't want to write the serializer/deserializer for each message.
Here's the code I used to generalize the deserialization from a byte[].
public virtual bool SetMessageBytes(byte[] message)
{
MemberInfo[] members = FormatterServices.GetSerializableMembers(this.GetType());
object[] values = FormatterServices.GetObjectData(this, members);
int j = 0;
for (int i = 0; i < members.Length; i++)
{
string[] var = members[i].ToString().Split(new char[] { ' ' });
switch (var[0])
{
case "UInt32":
values[i] = (UInt32)((message[j] << 24) + (message[j + 1] << 16) + (message[j + 2] << 8) + message[j + 3]);
j += 4;
break;
case "UInt16":
values[i] = (UInt16)((message[j] << 8) + message[j + 1]);
j += 2;
break;
case "Byte":
values[i] = (byte)message[j++];
break;
case "UInt32[]":
if (values[i] != null)
{
int len = ((UInt32[])values[i]).Length;
byte[] b = new byte[len * 4];
Array.Copy(message, j, b, 0, len * 4);
Array.Copy(Utilities.ByteArrayToUInt32Array(b), (UInt32[])values[i], len);
j += len * 4;
}
break;
case "Byte[]":
if (values[i] != null)
{
int len = ((byte[])values[i]).Length;
Array.Copy(message, j, (byte[])(values[i]), 0, len);
j += len;
}
break;
default:
throw new Exception("ByteExtractable::SetMessageBytes Unsupported Type: " + var[1] + " is of type " + var[0]);
}
}
FormatterServices.PopulateObjectMembers(this, members, values);
return true;
}