5

Let's say that I have a struct similar to

public struct MyStruct
{
    public float[] a;
}

and that I want to instantiate one such struct with some custom array size (let's say 2 for this example). Then I marshal it into a byte array.

MyStruct s = new MyStruct();
s.a = new float[2];
s.a[0] = 1.0f;
s.a[1] = 2.0f;

byte[] buffer = new byte[Marshal.SizeOf(typeof(MyStruct))];
GCHandle gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

try
{
    Marshal.StructureToPtr(s, gcHandle.AddrOfPinnedObject(), false);

    for (int i = 0; i < buffer.Length; i++)
    {
        System.Console.WriteLine(buffer[i].ToString("x2"));
    }
}
finally
{
    gcHandle.Free();
}

This gives me only 4 bytes in my byte[] and they look like a pointer value rather than the value of either 1.0f or 2.0f. I've searched around for ways to make this work, but all I've been able to find so far are similar examples where the struct array size is known ahead of time. Isn't there a way to do this?

3 Answers 3

9

The StructureToPtr does only work with structures which contains value types only (int, char, float, other structs). float[] is a reference type and so you really get a kind of pointer (you can't really use it because it is a managed pointer). If you want to copy your array to a pinned memory you have to use one of the Marshal.Copy functions directly on your s.a float array.

Something like that. (I didn't really test it)

byte[] buffer = new byte[sizeof(float) * s.a.Length];
GCHandle gcHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);

and then

Marshal.Copy(s.a, 0, gcHandle.AddrOfPinnedObject(), s.a.Length);

Update

I have to correct myself. You can get also get what you want by declaring your struct in this way:

 [StructLayout(LayoutKind.Sequential)]
 public struct MyStruct
 {
     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
     public float[] a;
 }

As you see you have to fix the size of the float array at design time.

Sign up to request clarification or add additional context in comments.

5 Comments

If you use Buffer.BlockCopy, you don't need to mess with pinning the buffer.
Unfortunately, I cannot use the SizeConst=N method because it assume I know the size of the structure at compile time. However, my array size changes at runtime.
I like the idea of Marshal.Copy or Buffer.BlockCopy however, I fear this would defeat the purpose trying to use the marshaler. The example struct I gave was VERY simplified. In reality my structure is a mishmash of value types and reference types, all intertwined. It seems like the only way for me to do this is to manually iterate through every element in the struct and serialize it in the manner that is appropriate for that element,
That would be the only way getting the Marshal.SizeOf and Marshal.StructureToPtr methods work correct on your whole struct.
Yes. Normally you shouldn't use structs in this way in C#. Structs should be very small, sould contain value types only and should immutable after creation (i.e. values are set only in constructor). If you use struct in other ways you often get problems which are difficult to detect. ... But you are right. You should solve your problem manually as your struct seems to be too complex to copy it in an automated way.
3

There's no straight-forward support in P/Invoke to enable your expected scenario. What I've coded is a technique similar to the one used for storing the Variable length array in a struct in C.

    [StructLayout(LayoutKind.Sequential)]
    public struct VarLenStruct
    {
        public int elementCount;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public float[] array;



        public static GCHandle GetUnmanagedStruct(VarLenStruct managedStruct)
        {
            if (managedStruct.elementCount < 1)
                throw new ArgumentOutOfRangeException("The array size must be non-zero");

            // Array is a contiguous data structure, so assign the memory appended to the struct for the elements other than the first
            int managedBufferSize = Marshal.SizeOf(managedStruct) + Marshal.SizeOf(typeof(float)) * (managedStruct.elementCount - 1);
            byte[] managedBuffer = new byte[managedBufferSize];

            var handle = GCHandle.Alloc(managedBuffer, GCHandleType.Pinned);
            try
            {
                IntPtr unmgdStructPtr = handle.AddrOfPinnedObject();
                Marshal.StructureToPtr(managedStruct, unmgdStructPtr, fDeleteOld: false);

                IntPtr unmgdArrAddr = unmgdStructPtr + Marshal.OffsetOf(typeof(VarLenStruct), "array").ToInt32();
                Marshal.Copy(source: managedStruct.array, startIndex: 0, destination: unmgdArrAddr, length: managedStruct.elementCount);
            }
            catch
            {
                // The handle must be freed in case of any error, since it won't be visible outside this method in this case
                handle.Free();
                throw;
            }

            return handle; // Make sure to free this handle at the end of usage!
        }

        public static VarLenStruct GetManagedStruct(IntPtr unmanagedStructPtr)
        {
            VarLenStruct resultStruct = (VarLenStruct)Marshal.PtrToStructure(unmanagedStructPtr, typeof(VarLenStruct));
            if (resultStruct.elementCount < 1)
                throw new NotSupportedException("The array size must be non-zero");

            Array.Resize(ref resultStruct.array, newSize: resultStruct.elementCount); // Since the above unmarshalling always gives us an array of Size 1
            IntPtr unmgdArrAddr = unmanagedStructPtr + Marshal.OffsetOf(typeof(VarLenStruct), "array").ToInt32();
            Marshal.Copy(source: unmgdArrAddr, destination: resultStruct.array, startIndex: 0, length: resultStruct.elementCount);

            return resultStruct;
        }
    }

    public static void TestVarLengthArr()
    {
        VarLenStruct[] structsToTest = new VarLenStruct[]{
            new VarLenStruct() { elementCount = 1, array = new float[] { 1.0F } },
            new VarLenStruct() { elementCount = 2, array = new float[] { 3.5F, 6.9F } },
            new VarLenStruct() { elementCount = 5, array = new float[] { 1.0F, 2.1F, 3.5F, 6.9F, 9.8F } }
        };

        foreach (var currStruct in structsToTest)
        {
            var unmgdStructHandle = VarLenStruct.GetUnmanagedStruct(currStruct);
            try
            {
                var ret = VarLenStruct.GetManagedStruct(unmgdStructHandle.AddrOfPinnedObject());
                if (!ret.array.SequenceEqual(currStruct.array))
                    throw new Exception("Code fail!");
            }
            finally
            {
                unmgdStructHandle.Free();
            }
        }
    }

I've currently blocked the use of empty array, but with some additional handling you can achieve that too. You can also add validations to your struct constructor in order to check array.Length == elementCount, etc.

1 Comment

Thank you for this! Saved my day! This one should be THE most liked solution!
1

Marshal is really for native interop (p/invoke and so on), and native languages don't allow structure members to vary in size at runtime either. (There is a trick called a flexible array member which can appear only at the end... you can deal with that merely by allocating extra memory beyond the structure size).

If you want to serialize an array of primitives to a byte array, just use Buffer.BlockCopy. Arrays of structures of primitives are harder, you can codegen a DynamicMethod which uses the cpblk MSIL instruction or p/invoke memcpy in msvcrt.dll or RtlCopyMemory in kernel32.dll.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.