使用反射来确定.Net类型在内存中的布局方式 [英] Using reflection to determine how a .Net type is layed out in memory

查看:149
本文介绍了使用反射来确定.Net类型在内存中的布局方式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm experimenting with optimizing parser combinators in C#. One possible optimization, when the serialized format matches the in-memory format, is to just do an (unsafe) memcpy of the data to be parsed over an instance or even many instances of the type.

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

I want to write code that determines if the in-memory format matches the serialized format, in order to dynamically determine if the optimization can be applied. (Obviously this is an unsafe optimization and might not work for a whole bunch of subtle reasons. I'm just experimenting, not planning to use this in production code.)

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

I use the attribute [StructLayout(LayoutKind.Sequential, Pack = 1)] to force no padding and to force the in-memory order to match declaration order. I check for that attribute with reflection, but really all this confirms is "no padding". I also need the order of the fields. (I would strongly prefer to not have to manually specified FieldOffset attributes for every field, since that would be very error prone.)

我假设我可以使用 GetFields ,但文档中明确指出未指定顺序.

I assumed I could use the order of fields returned by GetFields, but the documentation explicitly calls out that the order is unspecified.

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

Given that I am forcing the order of fields with the StructLayout attribute, is there a way to reflect on that ordering?

edit 我可以接受所有字段必须为可漂白.

edit I'm fine with the restriction that all of the fields must be blittable.

推荐答案

如果将LayoutKind.Sequential与可出血类型一起使用

This is unnecessary if using LayoutKind.Sequential with blittable types

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

You don't need to use reflection or any other mechanism to find out the order of struct fields in memory, as long as all the fields are blittable.

LayoutKind.Sequential声明的结构的可blittable字段将以声明字段的顺序在内存中.这就是LayoutKind.Sequential的意思!

The blittable fields for a struct declared with LayoutKind.Sequential will be in memory in the order in which the fields are declared. That's what LayoutKind.Sequential means!

从这里文档:

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

For blittable types, LayoutKind.Sequential controls both the layout in managed memory and the layout in unmanaged memory. For non-blittable types, it controls the layout when the class or structure is marshaled to unmanaged code, but does not control the layout in managed memory.

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

Note that this doesn't tell you how much padding each field is using. To find that out, see below.

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

To determine the field order when using LayoutKind.Auto, or the field offsets when using any layout

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

It's fairly easy to find the struct field offsets if you're happy to use unsafe code, and to not use reflection.

您只需要获取结构每个字段的地址,并计算从结构开始处的偏移量即可.了解每个字段的偏移量后,您可以计算它们的顺序(以及它们之间的任何填充字节).要计算用于最后一个字段(如果有)的填充字节,您还需要使用sizeof(StructType)获取结构的总大小.

You just need to take the address of each field of the struct and calculate its offset from the start of the struct. Knowing the offsets of each field, you can calculate their order (and any padding bytes between them). To calculate the padding bytes used for the last field (if any) you will also need to get the total size of the struct using sizeof(StructType).

以下示例适用于32位和64位.请注意,您不需要使用fixed关键字,因为该结构已经在栈上,因此已经固定(如果尝试将fixed与之配合使用,则会出现编译错误):

The following example works for 32-bit and 64-bit. Note that you don't need to use fixed keyword because the struct is already fixed due to it being on the stack (you'll get a compile error if you try to use fixed with it):

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

To determine the field offsets when using LayoutKind.Sequential

如果您的结构使用LayoutKind.Sequential,则可以使用 Marshal.OffsetOf() 直接获取偏移量,但这对LayoutKind.Auto不起作用:

If your struct uses LayoutKind.Sequential then you can use Marshal.OffsetOf() to get the offset directly, but this does not work with 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代码,而且代码更短-而且您无需事先知道字段名称.如上所述,不需要确定内存中字段的顺序,但是如果您需要了解使用了多少填充,这可能会很有用.

This is clearly a better way to do it if you are using LayoutKind.Sequential since it doesn't require unsafe code, and it's much shorter - and you don't need to know the names of the fields in advance. As I said above, it is not needed to determine the order of the fields in memory - but this might be useful if you need to find out about how much padding is used.

这篇关于使用反射来确定.Net类型在内存中的布局方式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆