There are quite a few questions on here about MemberwiseClone, but I can't find anything covering this exactly.
As I understand it, MemberwiseClone basically just copies an area of memory for an object, dumps it somewhere else and calls it a new instance of that object. Obviously very quick, and for large objects it is the quickest way to make a copy. For small objects with simple constructors, it's quicker to create an instance and copy the individual properties.
Now, I have a tight loop in which I'm using MemberwiseClone. As MemberwiseClone always creates a new instance this leads to a lot of memory allocation, and reallocation which isn't great for performance. I've written a custom copy for a very large object that copies all the properties individually onto an existing object, which overall is marginally quicker than using MemberwiseClone,
I feel that if I could grab the whole chunk of memory and dump it onto the existing object, I'd achieve a decent performance boost as the GC wouldn't have to worry about anything. Hopefully I'll also get rid of this message:
Just to clarify, I want to copy the properties from one existing object to another as quickly as possible. A shallow copy is all I need.
Message 2 DA0023: (Average)% Time in GC = 19.30;
% Time in GC is relatively high. This indication of excessive amount of garbage collection overhead could be impacting the
responsiveness of your application. You can gather .NET memory
allocation data and object lifetime information to understand the
pattern of memory allocation your application uses better.
I have no restrictions on code safety, speed is the aim of the game here.
No comments asking if this is really necessary or talking about premature optimisation please.
Thanks for your help.
Answer
Taking the advise below, of embedding a struct within the object and storing all properties in that. I could then take a copy of that struct to copy all the properties in a single assignment. This yielded more than a 50% speed increase compared to copying field by field, and about a 60% speed increase over creating a new object every time with MemberwiseClone.
If your class is not inheritable, you could store the entire state of your class in an exposed-field structure (expose fields, not properties!). This would require that you prefix all field names with the name of the structure, which would be ugly in the source code, but accessing fields in that structure will be just as fast as would be accessing fields that were directly placed in the enclosing class. Doing this, however, will gain you the ability to copy the structure from one object instance into another using a simple assignment statement.
In general you can't be faster than doing a copy field by field.
This in general. There is a single exception: if your class is blittable (so doesn't contain any reference to non-blittable classes) AND it's declared with the [StructLayout(LayoutKind.Sequential)] (or with the [StructLayout(LayoutKind.Explicit)], but it's more complex to test) then you can pin it
GCHandle handle = GCHandle.Alloc(refToYourClass, GCHandleType.Pinned);
from there a new world opens... You can directly copy byte-by-byte the content of your class with some unsafe (both as unsafe the keyboard and unsafe as "running-with-scissors") code.
BUT for this to work, your class MUST be blittable!
The fastest method is UnsafeCopy8 that copies blocks of 8 bytes (both at 32bits and at 64bits). The fastest PInvoke method is memcpy.
[StructLayout(LayoutKind.Sequential)]
class MyClass
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
public int D { get; set; }
public int E { get; set; }
public int F { get; set; }
public int G { get; set; }
public byte H { get; set; }
}
class Program
{
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);
[DllImport("kernel32.dll", SetLastError = false)]
static extern void CopyMemory(IntPtr dest, IntPtr src, UIntPtr count);
static void Main()
{
MyClass mc = new MyClass { A = 1, B = 2, C = 3, D = 4, E = 5, F = 6, G = 7, H = 8 };
MyClass mc2 = new MyClass();
int size = Marshal.SizeOf(typeof(MyClass));
var gc = GCHandle.Alloc(mc, GCHandleType.Pinned);
var gc2 = GCHandle.Alloc(mc2, GCHandleType.Pinned);
IntPtr dest = gc2.AddrOfPinnedObject();
IntPtr src = gc.AddrOfPinnedObject();
// Pre-caching
memcpy(dest, src, (UIntPtr)size);
CopyMemory(dest, src, (UIntPtr)size);
UnsafeCopy(dest, src, size);
UnsafeCopy8(dest, src, size);
int cycles = 10000000;
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
var sw1 = Stopwatch.StartNew();
for (int i = 0; i < cycles; i++)
{
memcpy(dest, src, (UIntPtr)size);
}
sw1.Stop();
var sw2 = Stopwatch.StartNew();
for (int i = 0; i < cycles; i++)
{
CopyMemory(dest, src, (UIntPtr)size);
}
sw2.Stop();
var sw3 = Stopwatch.StartNew();
for (int i = 0; i < cycles; i++)
{
UnsafeCopy(dest, src, size);
}
sw3.Stop();
var sw4 = Stopwatch.StartNew();
for (int i = 0; i < cycles; i++)
{
UnsafeCopy8(dest, src, size);
}
sw4.Stop();
gc.Free();
gc2.Free();
Console.WriteLine("IntPtr.Size: {0}", IntPtr.Size);
Console.WriteLine("memcpy: {0}", sw1.ElapsedTicks);
Console.WriteLine("CopyMemory: {0}", sw2.ElapsedTicks);
Console.WriteLine("UnsafeCopy: {0}", sw3.ElapsedTicks);
Console.WriteLine("UnsafeCopy8: {0}", sw4.ElapsedTicks);
Console.ReadKey();
}
static unsafe void UnsafeCopy(IntPtr dest, IntPtr src, int size)
{
while (size >= sizeof(int))
{
*((int*)dest) = *((int*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(int));
src = (IntPtr)(((byte*)src) + sizeof(int));
size -= sizeof(int);
}
if (size >= sizeof(short))
{
*((short*)dest) = *((short*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(short));
src = (IntPtr)(((byte*)src) + sizeof(short));
size -= sizeof(short);
}
if (size == sizeof(byte))
{
*((byte*)dest) = *((byte*)src);
}
}
static unsafe void UnsafeCopy8(IntPtr dest, IntPtr src, int size)
{
while (size >= sizeof(long))
{
*((long*)dest) = *((long*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(long));
src = (IntPtr)(((byte*)src) + sizeof(long));
size -= sizeof(long);
}
if (size >= sizeof(int))
{
*((int*)dest) = *((int*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(int));
src = (IntPtr)(((byte*)src) + sizeof(int));
size -= sizeof(int);
}
if (size >= sizeof(short))
{
*((short*)dest) = *((short*)src);
dest = (IntPtr)(((byte*)dest) + sizeof(short));
src = (IntPtr)(((byte*)src) + sizeof(short));
size -= sizeof(short);
}
if (size == sizeof(byte))
{
*((byte*)dest) = *((byte*)src);
}
}
}
Related
First of all I know I shouldn't even try to do it, but i like to try things that don't make sense. I was intrigued by this post and modified to my needs and I wanted to improve it by implementing allocation of arrays inside unmanaged memory.
public static class Unmanaged<T> where T : class
{
private delegate T CreateHandler(IntPtr ptr);
private delegate IntPtr FindHandler(T obj);
private static readonly CreateHandler Create;
private static readonly FindHandler Find;
private static readonly IntPtr _typePointer;
private static readonly int _typeSize;
static Unmanaged()
{
Type type = typeof(T);
_typePointer = type.TypeHandle.Value;
_typeSize = Marshal.ReadInt32(_typePointer, sizeof(int));
DynamicMethod method = new DynamicMethod(nameof(Create), typeof(T), new[] { typeof(IntPtr) }, typeof(Unmanaged<T>), true);
var generator = method.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ret);
Create = (CreateHandler)method.CreateDelegate(typeof(CreateHandler));
method = new DynamicMethod(nameof(Find), typeof(IntPtr), new[] { typeof(T) }, typeof(Unmanaged<T>), true);
generator = method.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ret);
Find = (FindHandler)method.CreateDelegate(typeof(FindHandler));
}
public static T New()
{
IntPtr handle = Marshal.AllocHGlobal(_typeSize);
IntPtr pointer = handle + IntPtr.Size;
Marshal.WriteIntPtr(pointer, _typePointer);
return Create(pointer);
}
public static void Destroy(T obj)
{
IntPtr pointer = Find(obj);
IntPtr handle = pointer - IntPtr.Size;
Marshal.FreeHGlobal(handle);
}
}
public static class UnmanagedArray<T>
{
private static readonly Delegate Create;
private static readonly Delegate Find;
private static readonly IntPtr _typePointer;
private static readonly int _typeSize;
static UnmanagedArray()
{
Type type = typeof(T[]);
Type createType = Expression.GetFuncType(typeof(IntPtr), typeof(T[]));
Type findType = Expression.GetFuncType(typeof(T[]), typeof(IntPtr));
_typePointer = type.TypeHandle.Value;
_typeSize = Marshal.ReadInt32(_typePointer, sizeof(int));
DynamicMethod method = new DynamicMethod(nameof(Create), typeof(T[]), new[] { typeof(IntPtr) }, typeof(UnmanagedArray<T>), true);
ILGenerator generator = method.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ret);
Create = method.CreateDelegate(createType);
method = new DynamicMethod(nameof(Find), typeof(IntPtr), new[] { typeof(T[]) }, typeof(UnmanagedArray<T>), true);
generator = method.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ret);
Find = method.CreateDelegate(findType);
}
public static T[] New(int count)
{
Type elementType = typeof(T);
int elementSize = GetElementSize(elementType);
int size = _typeSize + elementSize * count;
IntPtr handle = Marshal.AllocHGlobal(size);
IntPtr pointer = handle + IntPtr.Size;
Marshal.WriteIntPtr(pointer, _typePointer);
InitializeArray(handle, size, count);
return (T[])Create.DynamicInvoke(pointer);
}
public static void Destroy(T[] obj)
{
IntPtr pointer = (IntPtr)Find.DynamicInvoke(obj);
IntPtr handle = pointer - IntPtr.Size;
Marshal.FreeHGlobal(handle);
}
private static int GetElementSize(Type type)
{
if (type.IsValueType) return Marshal.SizeOf(type);
else return IntPtr.Size;
}
private static unsafe void InitializeArray(IntPtr handle, int size, int count)
{
int startPosition = IntPtr.Size * 2;
int endPosition = IntPtr.Size * 2 + sizeof(int);
byte byteSize = 8;
byte* bytes = (byte*)handle;
for (int index = startPosition, positionIndex = 0; index < endPosition; index++, positionIndex++)
{
bytes[index] = (byte)(count >> positionIndex * byteSize);
}
for (int index = _typeSize; index < size; index++)
{
bytes[index] = 0;
}
}
}
For value types (short, int, long etc.) it works (I haven't tried struct's yet), but it crashes after allocating array for objects as below...
static void Main(string[] args)
{
Test test = Unmanaged<Test>.New();
Test[] array = UnmanagedArray<Test>.New(2);
test.Value = 5;
array[0] = test;
Console.WriteLine(array[0].Value); //AccessViolationException
Unmanaged<Test>.Destroy(test);
UnmanagedArray<Test>.Destroy(array);
}
Don't know why it crashes with AccessViolationException (which You can't catch by normal ways) and the best part is that it's something that not always occurs. Checked stack when debugging and the array actually saves reference to object in unmanaged memory (even checked addresses and they match), but later on it crashes (very often)...
Any suggestions?
Update
Turns out You can't assign or read nested reference* when allocating unmanaged memory for reference Type. Probably it's related to how GC manages references (don't know for sure, it's just a guess). That's why in the link above the author encapsulated reference inside struct. That way You're working with value type and if You change array to store those structs (containing Your unmanaged references) it will work.
*By nested references I mean fields in class which You try to allocate space in unmanaged memory and array elements (array of objects itself is a reference on stack to actual array on heap - if it's not a field in a class - which contains "nested" references as elements to objects - also on heap)
GC won't be able to update pointers to other objects inside your unmanaged memory and doesn't count them as live references, so any pointer to managed memory inside your object will be either invalid or dead (twice invalid). As Marc said, the only thing you can place in unmaged memory is structs adhering to unmanaged constraint.
[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'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.
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;
}