使用reflection来确定.Net类型在内存中的布局方式

我正在尝试在C#中优化解析器组合器。 当序列化格式与内存格式匹配时,一种可能的优化是仅对要在实例上解析的数据的(不安全)memcpy甚至该类型的许多实例进行解析。

我想编写确定内存中格式是否与序列化格式匹配的代码,以便动态确定是否可以应用优化。 (显然这是一个不安全的优化,可能无法解决一大堆微妙的原因。我只是在尝试,而不是计划在生产代码中使用它。)

我使用属性[StructLayout(LayoutKind.Sequential,Pack = 1)]强制不填充并强制内存顺序匹配声明顺序。 我用reflection检查该属性,但实际上所有这些都证实是“无填充”。 我还需要字段的顺序。 (我非常希望不必为每个字段手动指定FieldOffset属性,因为这很容易出错。)

我假设我可以使用GetFields返回的字段顺序,但文档明确指出订单未指定。

鉴于我使用StructLayout属性强制字段的顺序,有没有办法反映该排序?

编辑我很好,所有字段必须是blittable的限制 。

如果使用带有blittable类型的LayoutKind.Sequential ,则不需要这样做

只要所有字段都是blittable,就不需要使用reflection或任何其他机制来查找内存中struct结构字段的顺序。

使用LayoutKind.Sequential声明的结构的blittable字段将按照声明字段的顺序在内存中。 这就是LayoutKind.Sequential意思!

从这个文件 :

对于blittable类型,LayoutKind.Sequential控制托管内存中的布局和非托管内存中的布局。 对于非blittable类型,它在将类或结构封送到非托管代码时控制布局,但不控制托管内存中的布局。

请注意,这并不能告诉您每个字段使用的填充量。 要找到它,请参阅下文。

使用LayoutKind.Auto时确定字段顺序,或使用任何布局时确定字段偏移

如果您乐意使用不安全的代码,并且使用reflection,那么很容易找到结构字段偏移量。

您只需要获取结构的每个字段的地址,并从结构的开头计算它的​​偏移量。 知道每个字段的偏移量,您可以计算它们的顺序(以及它们之间的任何填充字节)。 要计算用于最后一个字段(如果有)的填充字节,您还需要使用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时确定字段偏移量

如果您的struct使用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代码,并且它更短 – 而且您不需要事先知道字段的名称。 如上所述,不需要确定内存中字段的顺序 – 但如果您需要了解使用了多少填充,这可能很有用。

作为想要了解顺序和布局类型的人的参考。 例如,如果类型包含非blittable类型。

 var fields = typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); fields.SortByFieldOffset(); var isExplicit = typeof(T).IsExplicitLayout; var isSequential = typeof(T).IsLayoutSequential; 

它使用我写的扩展方法:

  public static void SortByFieldOffset(this FieldInfo[] fields) { Array.Sort(fields, (a, b) => OffsetOf(a).CompareTo(OffsetOf(b)) ); } private static int OffsetOf(FieldInfo field) { return Marshal.OffsetOf(field.DeclaringType, field.Name).ToInt32(); } 

MSDN包含有关IsLayoutSequential的有用信息。