Мультиплатформенная сериализация CArray MFC, 16, 32 и 64 бита

Я работаю над очень старым унаследованным кодом и переношу его с 32-битной на 64-битную версию.

Одна из вещей, с которыми я борюсь, связана с сериализацией MFC. Одним из различий между 32-битным и 64-битным был размер данных указателя. Это означает, например, что если по какой-то причине я сериализовал размер CArray как

ar << m_array.GetSize();

данные различались между платформами 32 и 64, потому что GetSize возвращает INT_PTR. Чтобы сериализовать данные, полностью совместимые с одним и тем же приложением, скомпилированным в 32- и 64-разрядной версии, я принудительно указал тип данных на этапе сохранения и то же самое при чтении. (почти уверен, что 32 бита достаточно для этих данных)

магазин

ar << (int)m_array.GetSize();

чтение

int iNumSize = 0;
ar >> iNumSize ;

Другими словами, приложение, независимо от того, скомпилировано оно в 32 или 64 бита, сериализует эти данные как int.

Теперь у меня есть одно сомнение по поводу сериализации типа CArray; для сериализации CArray кода используйте встроенную CArchive сериализацию

//defined as CArray m_arrayVertex; on .h
m_arrayVertex.Serialize(ar);

и это Serialize определено в файле MFC afxtemp.h с этим шаблоном

template<class TYPE, class ARG_TYPE>
void CArray<TYPE, ARG_TYPE>::Serialize(CArchive& ar)
{
    ASSERT_VALID(this);

    CObject::Serialize(ar);
    if (ar.IsStoring())
    {
        ar.WriteCount(m_nSize);
    }
    else
    {
        DWORD_PTR nOldSize = ar.ReadCount();
        SetSize(nOldSize, -1);
    }
    SerializeElements<TYPE>(ar, m_pData, m_nSize);
}

где (afx.h)

// special functions for reading and writing (16-bit compatible) counts
DWORD_PTR ReadCount();
void WriteCount(DWORD_PTR dwCount);

Вот мой вопрос: ReadCount и WriteCount используют DWORD_PTR, которые имеют разный размер на разных платформах... этот тип сериализации совместим с 32/64 битами или из-за изменения размера сериализованные данные работают только на каждой платформе соответственно?

Я имею в виду, что данные могут быть прочитаны как 32, так и 64 приложением без ошибок? в комментарии говорится, что это работает и для «16 бит», и я ничего не нашел в деталях об этой сериализации.

Если это не сработает, есть обходной путь для сериализации CArray таким образом, чтобы данные были полностью совместимы как с 32, так и с 64 приложением?

Обновлено: оба ответа хороши. Я просто принимаю решение в первую очередь. Большое спасибо обоим, надеюсь, может помочь кому-то еще!

Компиляция/совместимость зависит от платформы, но нет, реализация не "универсальна", если хотите. Исходный код MFC открыт (в C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.34.31933\atlmfc\sr‌​c\mfc в последних сборках Visual Studio), поэтому вы можете проверить все, что вы нужно самому. Вот он онлайн github.com/pixelspark/corespark/blob/master/Libraries/atlmfc‌​/…

Simon Mourier 10.01.2023 12:23

Спасибо, довольно полезно. Я читал код много раз, но я не уверен, как он работает, если данные, например, пишутся в 64 и читаются в 32. Если я правильно понял, это не универсально, но пока значение представимо с помощью 4 байта (32 бита), один и тот же файл должен читаться 32- и 64-битным приложением?

GiordiX 10.01.2023 13:47

Я всегда выбираю типы переменных, которые не меняются между редакциями.

Andrew Truckle 10.01.2023 13:49

@Андрей, что ты имеешь в виду? Если бы я создал CArray<userStruct_Int>, он должен быть доступен для чтения обеими платформами, если userStruct_Int является структурой со значением только int внутри?

GiordiX 10.01.2023 13:51

Я имею в виду, что я использую такие типы, как WORD, DWORD и т. д. int будет меняться между версиями бит, но такие типы, как WORD и т. д., остаются прежними. Не то, чтобы я не использовал CArray. Но я использовал CObArray.

Andrew Truckle 10.01.2023 13:52

Сериализация int всегда плохая идея, если вы намерены поддерживать сборку 32/64 вашего приложения для чтения данных, поскольку int имеет другой размер. Вот почему в предоставленных ответах все использует переменные, как я уже сказал. Поэтому, если вы собираетесь выполнять приведение, приведите к переменной, которая остается постоянной между различными версиями битов.

Andrew Truckle 10.01.2023 22:22

@AndrewTruckle OTOH int всегда 32-битный на платформе Windows.

Jabberwocky 11.01.2023 08:38

@Jabberwocky, по моему опыту, это не так. 32-битный и 64-битный int - это разница. Потому что у меня был перерыв CArchive из-за этого и, следовательно, необходимости использовать постоянные типы var.

Andrew Truckle 11.01.2023 09:00

@AndrewTruckle странно, только что попробовал printf("%zu\n", sizeof(int));. Он печатает 4 даже с x64. Использование Visual Studio 2022.

Jabberwocky 11.01.2023 09:09

@Jabberwocky Незнайка. Возможно, пользователь запускал Windows на Mac. Или, может быть, проблема с CArchive. Не беспокоиться.

Andrew Truckle 11.01.2023 09:17

С MFC я почти уверен, что только указатели меняют размер, int остается в 4 байта. Если вам нужны d и int с 8 байтами, вы должны использовать int64; это не совсем коррелирует с вопросом, трудно изменить устаревший код 15-летней давности, используя только некоторые конкретные данные.

GiordiX 11.01.2023 11:39

@AndrewTruckle int и long имеют ширину 32 бита в MSVC, независимо от целевой архитектуры. ОС, которая запускает код, не имеет значения; компилятор уже принял это решение. Больше информации здесь.

IInspectable 12.01.2023 01:37

@IInspectable Хорошо, может быть, тогда это были типы переменных INT_PTR. Странный. Но спасибо за информацию.

Andrew Truckle 12.01.2023 03:08
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
13
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Как вы написали, ReadCount возвращает DWORD_PTR, который имеет ширину 32 или 64 бита в зависимости от того, был ли код скомпилирован как 32- или 64-битный код.

Теперь, пока фактическое количество объектов укладывается в 32 бита, нет проблем с функциональной совместимостью между файлами, которые были написаны 32-битной или 64-битной программой.

С другой стороны, если ваш 64-битный код сериализует CArray, который имеет более 4294967295 элементов (что в любом случае маловероятно), у вас возникнут проблемы, если вы захотите прочитать десериализовать этот файл из 32-битной программы. Но в 32-битной программе CArray все равно не может хранить более 4294967295.

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

Не уверен, если я правильно понимаю. Обновление не проблема, но что если мы сериализуем (записываем) наши данные в компиляции x64, а затем десериализуем (читаем) обратно в компиляцию x32. Мы не должны беспокоиться об этом тоже?

Tom Tom 11.01.2023 08:26

@TomTom нет, это совсем не проблема. Прочтите также ответ IInspectable, который является гораздо более подробным.

Jabberwocky 11.01.2023 08:28

Хранение и извлечение количества элементов для экземпляров CArray реализованы в CArchive::WriteCount и CArchive::ReadCount соответственно.

Они записывают и считывают 16-битное (WORD), 32-битное (DWORD) или 64-битное (на 64-битных платформах, DWORD_PTR) значение в поток или из него. При написании используется следующий алгоритм:

  • Если количество предметов меньше 0xFFFF, запишите количество предметов в виде 16-битного WORD значения.
  • В противном случае выгрузите в поток маркер «недопустимого значения» ((WORD)0xFFFF), а затем
    • 32-битный: элемент считается как 32-битное значение (DWORD)
    • 64-битный: если количество элементов меньше 0xFFFF'FFFF, запишите количество элементов в виде 32-битного DWORD значения.
      • В противном случае выгрузите в поток маркер «недопустимого значения» ((DWORD)0xFFFFFFFF), за которым следует количество элементов в виде 64-битного значения (DWORD_PTR).

Схема потока представлена ​​в следующей таблице в зависимости от количества элементов в CArray (где ❌ обозначает значение, отсутствующее в потоке):

Количество предметов n WORDDWORDDWORD_PTR п < 0xFFFF н ❌ ❌ 0xFFFF <= n < 0xFFFF'FFFF 0xFFFF н ❌ n == 0xFFFF'FFFF (только 32-разрядная версия) 0xFFFF 0xFFFF'FFFF ❌ 0xFFFF'FFFF <= n (только 64-разрядная версия) 0xFFFF 0xFFFF'FFFF н

При десериализации потока код считывает значение счетчика элементов, проверяет, соответствует ли оно маркеру «недопустимое значение», и продолжает с большими значениями, если маркер был найден.

Это работает для разных разрядностей, пока CArray содержит не более 0xFFFF'FFFE значений. Для 32-битных платформ это всегда верно; у вас не может быть CArray, который использует все адресное пространство.

При сериализации из 64-битного процесса вам просто нужно убедиться, что в массиве не более 0xFFFF'FFFE элементов.


Резюме:

Для CArray с менее чем 0xFFFF'FFFF (4294967295) элементами сериализованный поток байт в байт идентичен независимо от того, был ли он создан на 32-разрядной или 64-разрядной платформе.

Есть странный угловой случай CArray с ровно 0xFFFF'FFFF элементами на 32-битной платформе1. Если бы это было передано и прочитано обратно на 64-битной платформе, поле размера в потоке было бы ошибочно принято за маркер «недопустимого значения», что привело бы к катастрофическим последствиям. К счастью, нам не о чем беспокоиться. 32-битные процессы не могут выделять контейнеры, размер которых кратен доступному адресному пространству.

Это охватывает сценарий, в котором поток, сериализованный на 32-разрядной платформе, используется на 64-разрядной платформе. На практике все работает как задумано.

Тогда в другом направлении: поток, созданный на 64-битной платформе, для десериализации на 32-битной платформе. Единственным уместным разногласием здесь являются контейнеры большего размера, чем может даже представить 32-битная программа. 64-битный сериализатор сбросит маркер «недопустимого значения» (DWORD), за которым следует фактическое количество элементов (DWORD_PTR)2. 32-битный десериализатор предположит, что маркер (0xFFFF'FFFF) является истинным количеством элементов, и завершит последующее выделение памяти, даже не взглянув на фактическое количество элементов. Вещи удаляются оттуда, используя любую обработку исключений, до того, как может произойти какое-либо повреждение данных3.

Однако это не новый режим ошибки, уникальный для кросс-битной совместимости. CArray, сериализованный на 32-битной платформе, может так же не десериализоваться на 32-битной платформе, если у процесса закончатся ресурсы. Это может произойти намного раньше, чем закончится нехватка памяти, поскольку CArray нужна непрерывная память.


1 Строка 3 в таблице выше.
2 Строка 4 в таблице выше.
3 Это предполагает, что в стеке вызовов нет catch(...), который просто продолжает игнорироваться.

В основном тот же ответ, что и мой, только лучше.

Jabberwocky 10.01.2023 14:39

@Jabberwocky Это объективно дольше. Насчет "лучше" не уверен. Некоторые люди всегда спешат, и слово «короткий» может больше соответствовать продолжительности их внимания.

IInspectable 10.01.2023 17:39

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