10

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

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

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

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

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

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

4

2 回答 2

5

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

于 2013-07-07T08:46:56.783 回答
3

作为那些想知道顺序和布局类型的人的参考。例如,如果一个类型包含非 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的有用信息。

于 2014-04-30T12:24:13.783 回答