9

If I marshal this struct with StructureToPtr and then unmarshal it again with PtrToStructure, my first node has y = {1,2} whilst my second node has y = {1,0}.

I've no idea why, perhaps my struct is bad somehow? Removing the bool from the struct makes it work.

using System;
using System.Runtime.InteropServices;

namespace csharp_test
{
    unsafe class Program
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct Node
        {
            public bool boolVar;
            public fixed int y[2];
        }

        unsafe static void Main(string[] args)
        {
            Node node = new Node();

            node.y[0] = 1;
            node.y[1] = 2;
            node.boolVar = true;

            int size = sizeof(Node);
            IntPtr ptr = Marshal.AllocHGlobal(size);
            Marshal.StructureToPtr(node, ptr, false);
            Node node2 = (Node)Marshal.PtrToStructure(ptr, typeof(Node));
            Marshal.FreeHGlobal(ptr);
        }
    }
}
junichiro
  • 5,282
  • 3
  • 18
  • 26
  • Maybe it has to do something with `bool` being marshaled as 4 bytes (`BOOL`) rather than 1 byte? But I can't think of the exact reason... – user541686 Feb 05 '12 at 19:37
  • Moreover, it ignores any array elements after the first one (writes them as zeros into the unmanaged memory). It also doesn't matter if `bool` comes before the array or after in the structure. – GSerg Feb 05 '12 at 19:42

2 Answers2

10

This indeed goes wrong. It is the StructureToPtr() call that fails to copy enough bytes. You can see this by using Debug + Windows + Memory + Memory1 and putting "ptr" in the address box. Using the sizeof operator isn't correct but not actually the source of the problem. Only the first element of the array is copied, regardless of the array length. Not sure what causes this problem, I never use fixed in pinvoke. I can only recommend the traditional pinvoke way which works fine:

unsafe class Program {
    [StructLayout(LayoutKind.Sequential)]
    public struct Node {
        public bool boolVar;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
        public int[] y;
    }

    unsafe static void Main(string[] args) {
        Node node = new Node();
        node.y = new int[2];

        node.y[0] = 1;
        node.y[1] = 2;
        node.boolVar = true;

        int size = Marshal.SizeOf(node);
        IntPtr ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(node, ptr, false);
        Node node2 = (Node)Marshal.PtrToStructure(ptr, typeof(Node));
        Marshal.FreeHGlobal(ptr);
    }

You can post to connect.microsoft.com if you want to bring this to the attention of the CLR interop masters.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • thanks, the comment in the link you post does indeed explain the problem, though actually the real answer is that I shouldn't be using fixed size buffers at all and should just use the MarshalAs attribute, as you demonstrate. I was already aware that you shouldn't really use sizeof(), just I'm not sure whether in the real world it makes any difference. But anyway, I have now changed my code to use Marshal.SizeOf regardless. – junichiro Feb 05 '12 at 20:19
  • @Hans a comments in your link are missing ☹ – Hi-Angel Oct 14 '14 at 09:47
  • Just to add to this: PtrToStructure() will also only copy the first byte of the fixed array, even if the memory location contains more info. Even if the structure uses LayoutKind.Explicit it still fails :( – Justin Stenning Feb 05 '16 at 09:29
0

You should also pack the struct or class before you use it. That works for me, almost as good as memcpy

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public class SomeClass
{
}
laalto
  • 150,114
  • 66
  • 286
  • 303