2012-01-27 64 views
5

我有一些代码用来从一个字节数组得到一个结构:为什么我不能使用Marshal.Copy()更新结构?

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,但那又有什么意义呢?

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

+0

出于好奇,什么是“正确”的方式? – Dmitry 2012-12-28 17:29:01

+0

@Dmitry,嗨,正确的方法是使用PtrToStructure()将指针传递给包含结构内容的非托管内存,如下所示:http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx 使用反射器可以看出PtrToStructure()实例化一个新的对象并填充它,虽然它如何做它我不确定,因为我相信这些细节是在我看不到的CLR(http://stackoverflow.com/questions/11788625/pinvoke-win32-function-for-marshal-ptrtostructure-in-silverlight-5) – sebf 2012-12-29 12:17:57

回答

4

警告实现细节警报,在未来的.Net版本中可能不会这样。

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

AddrOfPinnedObject的一点是要得到一个对象这就是内存的地址是固定的,而不是一个结构

此外,Eric Lippert已撰写关于参考类型和值类型主题的a series of very good blog posts

(*)除非:

1他们是一类
2它们被框字段
3他们是 “捕获变量”
4他们是在迭代器块

(NB点3和4是点1)

+1

需要注意的是'structs'可以存储在'heap'中,当它们是'class'成员时会发生这种情况。 – gdoron 2012-01-27 12:03:47

+0

@gdoron好点,回答更新。 – 2012-01-27 12:11:47

+0

这非常有意义,谢谢! – sebf 2012-01-27 13:25:16

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

但我会在这里使用显式布局,因为struct alignment

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

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

这就是你的问题开始。结构是一个值类型,GCHandle.Alloc()只能分配引用类型的句柄。这种类型的对象被分配在垃圾收集堆上。而那种让别人明智的做法。 C#编译器在这里有点太有用了,它会自动发出一个装箱转换,以便将该值框起来并使语句正常工作。这通常是非常好的,并产生了从System.Object派生值类型的错觉。呱呱叫,鸭子打字。

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

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

+0

+1谢谢你解释d的自动装箱;我完全可以看到我的代码是如何工作的(不是!)。 – sebf 2012-01-27 13:26:21

+2

值类型*是从System.Object派生的。这不是幻觉! – 2012-01-27 16:21:31

+0

当然。如果只有抽象将永远不会泄漏。 – 2012-01-27 16:36:18

相关问题