Использование отражения для определения расположения типа .Net в памяти.

Я экспериментирую с оптимизацией комбинаторов парсеров на С#. Одна из возможных оптимизаций, когда сериализованный формат соответствует формату в памяти, состоит в том, чтобы просто выполнить (небезопасный) memcpy данных, которые должны быть проанализированы для экземпляра или даже многих экземпляров типа.

Я хочу написать код, который определяет, соответствует ли формат в памяти сериализованному формату, чтобы динамически определять, можно ли применить оптимизацию. (Очевидно, что это небезопасная оптимизация и может не работать по целому ряду тонких причин. Я просто экспериментирую, не планирую использовать это в рабочем коде.)

Я использую атрибут [StructLayout(LayoutKind.Sequential, Pack = 1)], чтобы принудительно не заполнять и чтобы порядок в памяти соответствовал порядку объявления. Я проверяю этот атрибут с помощью отражения, но на самом деле все это подтверждает, что «без заполнения». Мне также нужен порядок полей. (Я бы предпочел не указывать вручную FieldOffset для каждого поля, так как это может привести к ошибкам.)

Я предполагал, что смогу использовать порядок полей, возвращаемый GetFields, но в документации явно указано, что порядок не указан.

Учитывая, что я задаю порядок полей с помощью атрибута StructLayout, есть ли способ отразить этот порядок?

редактировать Меня устраивает ограничение, согласно которому все поля должны быть преобразуемый.


person Craig Gidney    schedule 07.07.2013    source источник
comment
Не могли бы вы решить это, отражая эти атрибуты?   -  person It'sNotALie.    schedule 07.07.2013
comment
@newStackExchangeInstance Какие атрибуты?   -  person Craig Gidney    schedule 07.07.2013
comment
LayoutKind.Sequential управляет управляемым представлением только в том случае, если в структуре присутствуют только преобразовываемые типы. Если существует непреобразуемый тип, порядок полей в любом случае контролируется средой выполнения. Например. см. stackoverflow.com/q/14024483/11683.   -  person GSerg    schedule 07.07.2013
comment
Фактический макет типа в памяти выглядит так, как будто он будет полностью зависеть от реализации, и поэтому предлагаемая вами оптимизация не является стартовой. Что хорошего в эксперименте, если его никогда нельзя будет использовать в рабочем коде?   -  person Cody Gray    schedule 07.07.2013
comment
@CodyGray Я использую атрибут StructLayout, чтобы принудительно настроить макет. Он не должен меняться между реализациями, если только базовые значения не изменяются в размере (например, указатели). Иногда люди делают что-то для развлечения.   -  person Craig Gidney    schedule 07.07.2013
comment
@GSerg Приятно знать, что на самом деле есть типы, которые считаются преобразовываемыми. Итак, предполагая, что у меня есть структура, заполненная преобразовываемыми полями, как мне получить порядок?   -  person Craig Gidney    schedule 07.07.2013
comment
@CodyGray - мы постоянно делаем такие вещи, чтобы выжать последние капли производительности из наших торговых систем.   -  person hoodaticus    schedule 09.12.2016
comment
Если вас так заботит производительность, зачем писать код на C#, @hoo?   -  person Cody Gray    schedule 09.12.2016
comment
Я пишу код на нескольких языках, включая C++, CIL и ассемблер по мере необходимости. C# — отличный язык для объединения всего этого, потому что у него есть средства для точного управления расположением памяти. Он также служит прекрасным связующим звеном, позволяющим младшим разработчикам работать над другими частями проекта, такими как графический интерфейс или бизнес-код.   -  person hoodaticus    schedule 09.12.2016
comment
Я сделал все структуры (кроме обнуляемых github.com/invertedtomato/lightweight-serialization/issues/2, работа с нулевыми значениями). Я могу делать классы, если они несовместимы, но пытаюсь найти способ сделать все классы.   -  person Dzmitry Lahoda    schedule 12.03.2019


Ответы (2)


В этом нет необходимости при использовании LayoutKind.Sequential с преобразуемыми типами

Вам не нужно использовать отражение или какой-либо другой механизм, чтобы узнать порядок полей структуры в памяти, если все поля являются преобразовываемыми.

Преобразуемые поля для структуры, объявленной с помощью LayoutKind.Sequential, будут находиться в памяти в том порядке, в котором поля были объявлены. Вот что значит LayoutKind.Sequential!

Из этой документации:

Для непреобразуемых типов LayoutKind.Sequential управляет как макетом в управляемой памяти, так и макетом в неуправляемой памяти. Для непреобразуемых типов он управляет макетом, когда класс или структура маршалируются в неуправляемый код, но не управляет макетом в управляемой памяти.

Обратите внимание, что это не говорит вам, сколько заполнения использует каждое поле. Чтобы узнать это, смотрите ниже.

Чтобы определить порядок полей при использовании 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 и намного короче - и вам не нужно заранее знать имена полей. Как я сказал выше, нет необходимости определять порядок полей в памяти, но это может быть полезно, если вам нужно узнать, сколько используется отступов.

person Matthew Watson    schedule 07.07.2013
comment
Спасибо, использование различий указателей — это именно то, что мне нужно. Пока .Net запрещает любые оптимизации, если поля зачеркнуты или что-то в этом роде... - person Craig Gidney; 07.07.2013
comment
Я получаю ошибку «Не могу взять адрес данной ошибки компилятора выражения», когда пытаюсь применить оператор & к полю, например t.I. - person Craig Gidney; 08.07.2013
comment
@Strilanc Если вы скопируете и вставите мой код, он будет работать нормально, поэтому вы, должно быть, делаете что-то другое. Можете ли вы задать новый вопрос, почему то, что вы делаете, не сработает? По комментариям здесь поставить диагноз невозможно. Я знаю, что код, который я разместил, работает, и он также не содержит кода t.l (обратите внимание на строчные буквы l) нигде в нем, поэтому я знаю, что вы, должно быть, делаете что-то другое. :) - person Matthew Watson; 08.07.2013
comment
@Strilanc Это интересно - я никогда не пытался получить адрес поля только для чтения, поэтому я этого не знал! - person Matthew Watson; 08.07.2013
comment
@Strilanc Если вам сделать нужно это сделать, вы можете сделать это внутри конструктора для этой структуры (но вам нужно будет использовать ключевое слово fixed при получении адреса полей, если вы делаете это из конструктор). - person Matthew Watson; 08.07.2013

В качестве справки для тех, кто хочет знать порядок и вид макета. Например, если тип содержит непреобразуемые типы.

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.

person Herman    schedule 30.04.2014
comment
return fields.OrderBy(OffsetOf).ToArray() немного более лаконичен и неизменен для загрузки. - person Craig Gidney; 30.04.2014