Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么不能使用Marshal.Copy()更新结构?_C#_.net_Marshalling_Managed - Fatal编程技术网

C# 为什么不能使用Marshal.Copy()更新结构?

C# 为什么不能使用Marshal.Copy()更新结构?,c#,.net,marshalling,managed,C#,.net,Marshalling,Managed,我有一些代码打算从字节数组中获取结构: 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);

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

    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;
    }
public static T GetValue(byte[]data,int start),其中T:struct
{
td=默认值(T);
int elementsize=Marshal.SizeOf(typeof(T));
GCHandle sh=GCHandle.Alloc(d,GCHandleType.pinted);
Marshal.Copy(数据,开始,sh.AddrOfPinnedObject(),elementsize);
sh.Free();
返回d;
}
但是,结构
d
永远不会被修改,并且总是返回其默认值

我已经找到了“正确”的方法来做这件事,并且正在使用它,但是我仍然很好奇,因为我不明白为什么上面的方法不起作用

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


有人能告诉我为什么上述方法不起作用吗?警告实施详细信息警报,在未来的.Net版本中可能不会出现这种情况

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

addrofpindedobject
的要点是获取内存被固定的对象的地址,而不是结构

此外,Eric Lippert还写过关于引用类型和值类型的文章

(*)除非:

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


(注意,第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);
        }
    }
}
这就是你的问题开始的地方。结构是值类型,GCHandle.Alloc()只能为引用类型分配句柄。在垃圾收集堆上分配其对象的类型。以及那种使钉住变得合理的方法。C#编译器在这里有点太有用了,它会自动发出一个装箱转换来装箱值并使语句工作。这通常非常好,并且会产生一种错觉,认为值类型是从System.Object派生的。像鸭子一样的庸医打字

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


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

应该注意,
结构
可以存储在
堆上
,当它们是
类的成员时就会发生这种情况
+1感谢您解释d的自动装箱;我现在完全可以看到我的代码是如何工作的(不是!)。值类型是从System.Object派生的。这不是幻觉!当然如果抽象永远不会泄漏就好了。只是出于好奇,什么是“正确”的方法?@Dmitry,您好,正确的方法是使用PtrToStructure()传递一个指向包含结构内容的非托管内存的指针,如下所述:使用reflector可以看到PtrToStructure()实例化一个新对象并填充它,虽然它是如何做到的,但我不确定,因为我相信这些细节在CLR中,我看不到()
[StructLayout(LayoutKind.Explicit, Size = 3)]
public struct TestStruct
{
    [FieldOffset(0)]
    public byte z;

    [FieldOffset(1)]
    public short y;
}
    GCHandle sh = GCHandle.Alloc(d, GCHandleType.Pinned);