【问题标题】:Why can I not use Marshal.Copy() to update a struct?为什么我不能使用 Marshal.Copy() 来更新结构?
【发布时间】:2025-12-21 12:30:12
【问题描述】:

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

    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,获取指向它的指针,将一些字节复制到 this 指向的内存中,然后返回。 不仅如此,而且当我使用类似的代码但 d 是 T 的数组时,它可以正常工作。 除非 sh.AddrOfPinnedObject() 并不是真正指向d,但那有什么意义呢?

谁能告诉我为什么上述方法不起作用?

【问题讨论】:

  • 出于好奇,什么是“正确”的方式?
  • @Dmitry,嗨,正确的方法是使用 PtrToStructure() 传递指向包含结构内容的非托管内存的指针,如下所述:msdn.microsoft.com/en-us/library/4ca6d5z7.aspx 使用反射器可以看到 PtrToStructure()实例化一个新对象并填充它,虽然我不确定它是如何做到的,因为我认为这些细节在我看不到的 CLR 中 (*.com/questions/11788625/…)

标签: c# .net marshalling managed


【解决方案1】:
    GCHandle sh = GCHandle.Alloc(d, GCHandleType.Pinned);

这就是你的问题开始的地方。 struct 是一个值类型,GCHandle.Alloc() 只能为引用类型分配句柄。在垃圾收集堆上分配对象的种类。以及使钉扎变得明智的那种。 C# 编译器在这里有点太有用了,它会自动发出装箱转换来装箱值并使语句工作。这通常非常好,并且会产生值类型派生自 System.Object 的错觉。像鸭子一样的嘎嘎打字。

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

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

【讨论】:

  • +1 谢谢你解释d的自动装箱;我现在可以完全看到我的代码是如何(不是!)工作的。
  • 值类型从 System.Object 派生的。这不是幻觉!
  • 当然。如果只有抽象永远不会泄漏。
【解决方案2】:

Warning implementation detail alert,在未来的 .Net 版本中可能不是这样。

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

AddrOfPinnedObject 的目的是获取一个 object 的地址,它的内存是固定的,而不是 struct

此外,Eric Lippert 就引用类型和值类型的主题撰写了 a series of very good blog posts

(*) 除非:

1 它们是类中的字段
2 盒装
3 它们是“捕获的变量”
4 它们在一个迭代器块中

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

【讨论】:

  • 应该注意structs可以存储在heap上,当他们是class的成员时会发生这种情况
【解决方案3】:

这是一个工作示例:

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

但由于struct alignment,我会在这里使用显式布局。

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

    [FieldOffset(1)]
    public short y;
}

【讨论】:

    最近更新 更多