为什么我不能使用Marshal.Copy()来更新结构

本文关键字:更新 结构 Copy Marshal 不能 为什么 | 更新日期: 2023-09-27 18:21:31

我有一些代码旨在从字节数组中获取结构:

    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;
    }

但是,结构d永远不会被修改,并且总是返回其默认值。

我已经找到了"正确"的方法,并正在使用它,但我仍然很好奇,因为我不明白为什么上面的方法不起作用。

它尽可能简单:分配一些内存,d,获取一个指向它的指针,将一些字节复制到该指针指向的内存中,返回。不仅如此,而且当我使用类似的代码,但d是T的数组时,它工作得很好除非sh.AddrOfPinnedObject()并没有真正指向d,但它的意义是什么?

有人能告诉我为什么以上不起作用吗

为什么我不能使用Marshal.Copy()来更新结构

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

这就是你的问题开始的地方。结构是值类型,GCHandle.Alloc()只能为引用类型

问题是,Marshal.Copy()将更新值的盒装副本不是您的变量。所以你看不到它会改变。

只有使用Marshal.PtrToStructure()才能直接更新结构值。它包含将结构的已发布布局(StructLayout属性)转换为内部布局所需的智能。这是不一样的,在其他方面是无法发现的。

警告实现细节警报,在未来版本的.Net.中可能不会出现这种情况

structs是值类型,(通常)存储在堆栈(*)上,而不是堆上。结构的地址是没有意义的,因为它们是通过值传递的,而不是通过引用传递的。struct的数组是一个引用类型,它是指向堆上内存的指针,因此内存中的地址是完全有效的。

AddrOfPinnedObject的目的是获取内存被固定的对象的地址,而不是结构

此外,Eric Lippert还写了一系列关于参考类型和价值类型的非常好的博客文章。

(*)除非:

1它们是类上的字段
2它们是盒装的
3它们是"捕获的变量"
4它们在迭代器块中

(nb点3和4是点1的推论)

下面是一个工作示例:

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);
        }
    }
}

但由于结构对齐,我会在这里使用显式布局。

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