我有一些代码用于从字节数组中获取结构:
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 :(得分:9)
GCHandle sh = GCHandle.Alloc(d, GCHandleType.Pinned);
这就是你的问题开始的地方。 struct是值类型,GCHandle.Alloc()只能为引用类型分配句柄。在垃圾收集堆上分配对象的类型。而那种使钉扎变得明智的东西。 C#编译器在这里有点太有用了,它会自动发出一个装箱转换来装箱值并使语句有效。这通常非常好,并且产生了从System.Object派生值类型的错觉。嘎嘎叫鸭子打字。
问题是,Marshal.Copy()将更新值的盒装副本。 不您的变量。所以你看不到它的变化。
只有使用Marshal.PtrToStructure()才能直接更新结构值。它包含将结构的已发布布局(StructLayout属性)转换为内部布局所需的智能。这是不一样的,否则是不可发现的。
答案 1 :(得分:5)
警告实施细节警报,在.Net的未来版本中可能不是这样。
structs
是值类型,并且(通常)存储在堆栈(*)上,而不是堆上。结构的地址没有意义,因为它们是按值传递的,而不是通过引用传递的。 struct数组是一个引用类型,它是指向堆上内存的指针,因此内存中的地址完全有效。
AddrOfPinnedObject
的目的是获取内存固定的对象的地址,而不是 struct 。
此外,Eric Lippert撰写了a series of very good blog posts关于参考类型和价值类型的主题。
(*)除非:
1他们是班上的字段
2它们是盒装的
3它们是“捕获变量”
4它们位于迭代器块中
(nb点3和4是第1点的推论)
答案 2 :(得分: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;
}