Исключить дополнительное частное поле в структуре с помощью LayoutKind.Explicit из части макета структуры

Допустим, у нас есть одна структура:

      [StructLayout(LayoutKind.Explicit, Size=8)] // using System.Runtime.InteropServices;
public struct AirportHeader {
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.I4)]
    public int Ident; // a 4 bytes ASCII : "FIMP" { 0x46, 0x49, 0x4D, 0x50 }
    [FieldOffset(4)]
    [MarshalAs(UnmanagedType.I4)]
    public int Offset;
}

Что я хочу иметь : как прямой доступ к типу string и значения для поля в этой структуре, не нарушая размер структуры 8 байтов и не вычисляя каждый раз строковое значение из значения int.

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

Вопрос : Есть ли способ определить поле, не являющееся частью компоновки структуры? Нравиться :

      [StructLayout(LayoutKind.Explicit, Size=8)]
public struct AirportHeader {
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.I4)]
    public int Ident; // a 4 bytes ASCII : "FIMP" { 0x46, 0x49, 0x4D, 0x50 }
    [FieldOffset(4)]
    [MarshalAs(UnmanagedType.I4)]
    public int Offset;
    
    [NoOffset()] // <- is there something I can do the like of this
    string _identStr;
    public string IdentStr {
        get { // EDIT ! missed the getter on this property
            if (string.IsNullOrEmpty(_identStr)) _identStr =
                System.Text.Encoding.ASCII.GetString(Ident.GetBytes());
            // do the above only once. May use an extra private bool field to go faster.
            return _identStr;
        }
    }
}

PS : я использую указатели ('*' и '&', небезопасно), потому что мне нужно иметь дело с порядком байтов (локальная система, двоичные файлы / формат файла, сеть) и быстрым преобразованием типов, быстрым заполнением массивов. Я также использую множество вкусов Marshalметоды (фиксация структур в байтовых массивах) и немного PInvoke и COM-взаимодействия. Жаль, что некоторые сборки, с которыми я имею дело, еще не имеют своего аналога в dotNet.


TL; DR; Только для подробностей

Вопрос в том, о чем идет речь, я просто не знаю ответа. Следующее должно ответить на большинство вопросов, таких как « другие подходы » или « почему бы не сделать это вместо этого », но может быть проигнорировано, поскольку ответ будет прямым. Во всяком случае, я заранее все поставил, чтобы с самого начала было понятно, что я пытаюсь сделать. :)

Параметры / обходной путь, который я сейчас использую (или собираюсь использовать):

  1. Создайте геттер (а не поле), который каждый раз вычисляет строковое значение:

            public string IdentStr {
        get { return System.Text.Encoding.ASCII.GetString(Ident.GetBytes()); }
        // where GetBytes() is an extension method that converts an int to byte[]
    }
    

    Этот подход, хотя и выполняет свою работу, работает плохо: графический интерфейс отображает самолеты из базы данных полетов по умолчанию и вводит другие полеты из сети с частотой обновления в одну секунду (я должен увеличить ее до 5 секунд). У меня около 1200 рейсов в пределах области, относящейся к 2400 аэропортам (вылет и прилет), то есть у меня есть 2400 вызовов вышеуказанного кода каждую секунду для отображения идентификатора в DataGrid.

  2. Создайте другую структуру (или класс), единственной целью которой является управление данными на стороне графического интерфейса пользователя, когда не выполняется чтение / запись в поток или файл. Это означает, что считайте данные с явной структурой макета. Создайте другую структуру со строковой версией поля. Работа с графическим интерфейсом. Это будет работать лучше с общей точки зрения, но в процессе определения структур для двоичных файлов игры у меня уже есть 143 структуры такого типа (только со старыми версиями игровых данных; есть куча, которую я не заметил. пока не пишу, и я планирую добавить структуры для новейших типов данных). Банкоматы, более чем половине из них требуется одно или несколько дополнительных полей для значимого использования. Ничего страшного, если бы я был единственным, кто использовал сборку, но другие пользователи, вероятно, заблудятся с AirportHeader, AirportHeaderEx, AirportEntry, AirportEntryEx, AirportCoords, AirportCoordsEx.... Я бы этого не делал.

  3. Оптимизируйте вариант 1, чтобы вычисления выполнялись быстрее (благодаря SO есть куча идей, которые нужно искать - в настоящее время над идеей работают). Для поля Ident, я думаю, я мог бы использовать указатели (и я буду). Уже делая это для полей, я должен отображать их с прямым порядком байтов, а читать / писать - с прямым порядком байтов. Существуют и другие значения, например информация о сетке 4x4, которые упакованы в один Int64 (ulong), для которых требуется сдвиг битов, чтобы отобразить фактические значения. То же самое для идентификаторов GUID или объектов по тангажу / крену / рысканью.

  4. Попробуйте воспользоваться перекрывающимися полями (на учебе). Это будет работать для GUID. Возможно, это может сработать для примера Ident, если MarshalAs может ограничить значение строкой ASCII. Затем мне просто нужно указать то же поле FieldOffset, в данном случае «0». Но я не уверен, что устанавливаю значение поля ( entry.FieldStr = "FMEP";) фактически использует ограничение Marshal на стороне управляемого кода. Я не понимаю, что он будет хранить строку в Unicode на управляемой стороне (?). Более того, это не сработает для упакованных битов (байтов, содержащих несколько значений, или последовательных байтов, содержащих значения, которые необходимо сдвинуть по битам). Я считаю, что невозможно указать положение, длину и формат значения на уровне битов.

Зачем беспокоиться ? контекст :

Я определяю набор структур для анализа двоичных данных из массива байтов (IO.File.ReadAllBytes) или потоков и записи их обратно, данных, связанных с игрой. Логика приложения должна использовать структуры для быстрого доступа к данным и управления ими по запросу. Ожидаемые возможности сборки - это чтение, проверка, редактирование, создание и запись вне области действия игры (создание аддонов, управление) и внутри области действия игры (API, модификация в реальном времени или мониторинг). Другая цель - понять содержимое двоичных файлов (шестнадцатеричный) и использовать это понимание для создания того, чего не хватает в игре.

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

Мне нужно выяснить доверие и юридические вопросы (данные, защищенные авторским правом), но это выходит за рамки основной проблемы. Если на то пошло, то Microsoft действительно предоставляла на протяжении многих лет общедоступные свободно доступные SDK, раскрывающие структуры двоичных файлов в предыдущих версиях игры, для того, что я делаю (я не первый и, вероятно, не последний, кто это сделал). Тем не менее, я бы не осмелился раскрыть недокументированные двоичные файлы (например, для последних игровых данных) или способствовать нарушению авторских прав на материалы / двоичные файлы, защищенные авторским правом.

Я просто прошу подтверждения, есть ли способ, чтобы частные поля не были частью макета структуры. Наивная вера в банкомат - «это невозможно, но есть обходные пути». Просто мой опыт работы с C # довольно скудный, так что, возможно, я ошибаюсь, почему я спрашиваю. Спасибо !

2 ответа

Решение

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

Вы также не можете использовать указатель в качестве string в большинстве мест.

Если количество различных возможных строк относительно невелико (а, вероятно, так и будет, учитывая, что это всего 4 символа), вы можете использовать статический Dictionary<int, string>как своего рода механизм интернирования струн .

Затем вы пишете свойство для добавления / извлечения реальной строки.

Обратите внимание, что доступ к словарю O(1), и хеширование int просто возвращает себя, так что это будет очень, очень быстро, но займет некоторую память.

      [StructLayout(LayoutKind.Explicit, Size=8)]
public struct AirportHeader
{
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.I4)]
    public int Ident; // a 4 bytes ASCII : "FIMP" { 0x46, 0x49, 0x4D, 0x50 }

    [FieldOffset(4)]
    [MarshalAs(UnmanagedType.I4)]
    public int Offset;
    

    static Dictionary<int, string> _identStrings = new Dictionary<int, string>();

    public string IdentStr =>
        _identStrings.TryGetValue(Ident, out var ret) ? ret :
            (_identStrings[Ident] = Encoding.ASCII.GetString(Ident.GetBytes());
}

Это невозможно, потому что структура должна содержать все свои значения в определенном порядке. Обычно этот порядок контролируется самой CLR. Если вы хотите изменить порядок данных, вы можете использовать StructLayout. Однако вы не можете исключить поле, иначе данные просто не будут существовать в памяти.

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

Другие вопросы по тегам