简体   繁体   中英

Why can I not use Marshal.Copy() to update a struct?

I have some code intended to get a struct from a byte array:

    public static T GetValue<T>(byte[] data, int start) where T : struct
    {
        T d = default(T);
        int elementsize = Marshal.SizeOf(typeof(T));

        GCHandle sh = GCHandle.Alloc(d, GCHandleType.Pinned);
        Marshal.Copy(data, start, sh.AddrOfPinnedObject(), elementsize);
        sh.Free();

        return d;
    }

However, the structure d is never modified, and always returns its default value.

I have looked up the 'correct' way to do this and am using that instead, but am still curious, as I cannot see why the above should not work.

Its as simple as can be: allocate some memory, d, get a pointer to it, copy some bytes into the memory pointed at by this, return. Not only that, but when I use similar code but with d being an array of T, it works fine. Unless sh.AddrOfPinnedObject() isn't really pointing to d , but then what is the point of it?

Can anyone tell me why the above does not work?

    GCHandle sh = GCHandle.Alloc(d, GCHandleType.Pinned);

That's where your problem started. A struct is a value type , GCHandle.Alloc() can only allocate handles for reference types . The kind whose objects are allocated on the garbage collected heap. And the kind that make pinning sensible. The C# compiler is being a bit too helpful here, it automatically emits a boxing conversion to box the value and make the statement work. Which is normally very nice and creates the illusion that value types are derived from System.Object. Quacks-like-a-duck typing.

Problem is, Marshal.Copy() will update the boxed copy of the value. Not your variable. So you don't see it change.

Directly updating the structure value is only possible with Marshal.PtrToStructure(). It contains the required smarts to convert the published layout of the struct (StructLayout attribute) to the internal layout. Which is not the same and otherwise undiscoverable.

Warning implementation detail alert, this may not be true in future versions of .Net.

structs are value types and are (generally) stored on the stack (*) , not on the heap. An address of a struct is meaningless since they are passed by value, not by reference. The array of struct is a reference type, that is a pointer to memory on the heap, so the address in memory is perfectly valid.

The point of AddrOfPinnedObject is to get the address of an object thats memory is pinned, not a struct .

Additionally, Eric Lippert has written a series of very good blog posts on the subject of reference types and value types.

(*) Unless:

1 They are fields on a class
2 They are boxed
3 They are "captured variables"
4 They are in an iterator block

(nb points 3 and 4 are corollaries of point 1)

Here's a working example:

public static T GetValue<T>(byte[] data, int start) where T : struct
{
    int elementsize = Marshal.SizeOf(typeof(T));

    IntPtr ptr = IntPtr.Zero;

    try
    {
        ptr = Marshal.AllocHGlobal(elementsize);

        Marshal.Copy(data, start, ptr, elementsize);
        return (T)Marshal.PtrToStructure(ptr, typeof(T));
    }
    finally
    {
        if (ptr != IntPtr.Zero)
        {
            Marshal.FreeHGlobal(ptr);
        }
    }
}

But I would use explicit layout here because of struct alignment .

[StructLayout(LayoutKind.Explicit, Size = 3)]
public struct TestStruct
{
    [FieldOffset(0)]
    public byte z;

    [FieldOffset(1)]
    public short y;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM