繁体   English   中英

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

[英]Using reflection to determine how a .Net type is layed out in memory

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

我想编写确定内存中格式是否与序列化格式匹配的代码,以便动态确定是否可以应用优化。 (显然,这是一个不安全的优化,由于种种微妙的原因,它可能无法正常工作。我只是在做实验,而不打算在生产代码中使用它。)

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

我以为我可以使用GetFields返回的字段顺序,但是文档明确指出该顺序未指定。

鉴于我要使用StructLayout属性强制字段的顺序,是否有办法反映这种顺序?

编辑我可以限制所有字段必须是blittable的限制

如果将LayoutKind.Sequential与blittable类型一起使用,则不必要

您不需要使用反射或任何其他机制来查找结构字段在内存中的顺序,只要所有字段都是可蓝调的即可。

对于声明的结构的blittable领域LayoutKind.Sequential将在其中字段声明的顺序存储。 这就是LayoutKind.Sequential意思!

从本文档中

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

请注意,这不会告诉您每个字段使用了多少填充。 要找出答案,请参阅下文。

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

如果您愿意使用不安全的代码并且使用反射,则很容易找到struct字段的偏移量。

您只需要获取结构每个字段的地址,并计算从结构开始处的偏移量即可。 了解每个字段的偏移量后,您可以计算它们的顺序(以及它们之间的任何填充字节)。 要计算用于最后一个字段(如果有的话)的填充字节,您还需要使用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代码,并且代码要短得多,并且您不需要事先知道字段的名称。 就像我在上面说的那样,不需要确定字段在内存中的顺序-但是如果您需要了解使用了多少填充,这可能会很有用。

为那些想知道顺序和布局类型的人提供参考。 例如,如果一个类型包含不可引用的类型。

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的有用信息。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM