LayoutKind.Sequential
如果与 blittable 类型一起使用,则这是不必要的
您不需要使用反射或任何其他机制来找出内存中结构字段的顺序,只要所有字段都是 blittable 即可。
用声明的结构的 blittable 字段LayoutKind.Sequential
将按照声明字段的顺序在内存中。就是这个LayoutKind.Sequential
意思!
从此文档:
对于 blittable 类型,LayoutKind.Sequential 控制托管内存中的布局和非托管内存中的布局。对于非 blittable 类型,当类或结构编组为非托管代码时,它控制布局,但不控制托管内存中的布局。
请注意,这并不能告诉您每个字段使用了多少填充。要找出答案,请参见下文。
确定使用时的字段顺序LayoutKind.Auto
,或使用任何布局时的字段偏移量
如果您乐于使用不安全的代码并且不使用反射,那么很容易找到结构字段偏移量。
您只需要获取结构的每个字段的地址并计算其从结构开始的偏移量。知道每个字段的偏移量,您可以计算它们的顺序(以及它们之间的任何填充字节)。要计算用于最后一个字段的填充字节(如果有),您还需要使用sizeof(StructType)
.
以下示例适用于 32 位和 64 位。请注意,您不需要使用fixed
关键字,因为该结构已被固定,因为它在堆栈上(如果您尝试使用fixed
它,您将收到编译错误):
using System;
using System.Runtime.InteropServices;
namespace Demo
{
[StructLayout(LayoutKind.Auto, Pack = 1)]
public struct TestStruct
{
public int I;
public double D;
public short S;
public byte B;
public long L;
}
class Program
{
void run()
{
var t = new TestStruct();
unsafe
{
IntPtr p = new IntPtr(&t);
IntPtr pI = new IntPtr(&t.I);
IntPtr pD = new IntPtr(&t.D);
IntPtr pS = new IntPtr(&t.S);
IntPtr pB = new IntPtr(&t.B);
IntPtr pL = new IntPtr(&t.L);
Console.WriteLine("I offset = " + ptrDiff(p, pI));
Console.WriteLine("D offset = " + ptrDiff(p, pD));
Console.WriteLine("S offset = " + ptrDiff(p, pS));
Console.WriteLine("B offset = " + ptrDiff(p, pB));
Console.WriteLine("L offset = " + ptrDiff(p, pL));
Console.WriteLine("Total struct size = " + sizeof(TestStruct));
}
}
long ptrDiff(IntPtr p1, IntPtr p2)
{
return p2.ToInt64() - p1.ToInt64();
}
static void Main()
{
new Program().run();
}
}
}
使用时确定场偏移LayoutKind.Sequential
如果您的结构使用LayoutKind.Sequential
,那么您可以使用Marshal.OffsetOf()
直接获取偏移量,但这不适用于LayoutKind.Auto
:
foreach (var field in typeof(TestStruct).GetFields())
{
var offset = Marshal.OffsetOf(typeof (TestStruct), field.Name);
Console.WriteLine("Offset of " + field.Name + " = " + offset);
}
如果您正在使用,这显然是一种更好的方法,LayoutKind.Sequential
因为它不需要unsafe
代码,而且更短 - 而且您不需要提前知道字段的名称。正如我上面所说,不需要确定内存中字段的顺序 - 但如果您需要了解使用了多少填充,这可能很有用。