Я работаю над очень старым унаследованным кодом и переношу его с 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 приложением?
Обновлено: оба ответа хороши. Я просто принимаю решение в первую очередь. Большое спасибо обоим, надеюсь, может помочь кому-то еще!
Спасибо, довольно полезно. Я читал код много раз, но я не уверен, как он работает, если данные, например, пишутся в 64 и читаются в 32. Если я правильно понял, это не универсально, но пока значение представимо с помощью 4 байта (32 бита), один и тот же файл должен читаться 32- и 64-битным приложением?
Я всегда выбираю типы переменных, которые не меняются между редакциями.
@Андрей, что ты имеешь в виду? Если бы я создал CArray<userStruct_Int>, он должен быть доступен для чтения обеими платформами, если userStruct_Int является структурой со значением только int внутри?
Я имею в виду, что я использую такие типы, как WORD
, DWORD
и т. д. int
будет меняться между версиями бит, но такие типы, как WORD
и т. д., остаются прежними. Не то, чтобы я не использовал CArray
. Но я использовал CObArray
.
Сериализация int
всегда плохая идея, если вы намерены поддерживать сборку 32/64 вашего приложения для чтения данных, поскольку int
имеет другой размер. Вот почему в предоставленных ответах все использует переменные, как я уже сказал. Поэтому, если вы собираетесь выполнять приведение, приведите к переменной, которая остается постоянной между различными версиями битов.
@AndrewTruckle OTOH int
всегда 32-битный на платформе Windows.
@Jabberwocky, по моему опыту, это не так. 32-битный и 64-битный int - это разница. Потому что у меня был перерыв CArchive из-за этого и, следовательно, необходимости использовать постоянные типы var.
@AndrewTruckle странно, только что попробовал printf("%zu\n", sizeof(int));
. Он печатает 4
даже с x64. Использование Visual Studio 2022.
@Jabberwocky Незнайка. Возможно, пользователь запускал Windows на Mac. Или, может быть, проблема с CArchive. Не беспокоиться.
С MFC я почти уверен, что только указатели меняют размер, int остается в 4 байта. Если вам нужны d и int с 8 байтами, вы должны использовать int64; это не совсем коррелирует с вопросом, трудно изменить устаревший код 15-летней давности, используя только некоторые конкретные данные.
@AndrewTruckle int
и long
имеют ширину 32 бита в MSVC, независимо от целевой архитектуры. ОС, которая запускает код, не имеет значения; компилятор уже принял это решение. Больше информации здесь.
@IInspectable Хорошо, может быть, тогда это были типы переменных INT_PTR. Странный. Но спасибо за информацию.
Как вы написали, ReadCount
возвращает DWORD_PTR
, который имеет ширину 32 или 64 бита в зависимости от того, был ли код скомпилирован как 32- или 64-битный код.
Теперь, пока фактическое количество объектов укладывается в 32 бита, нет проблем с функциональной совместимостью между файлами, которые были написаны 32-битной или 64-битной программой.
С другой стороны, если ваш 64-битный код сериализует CArray
, который имеет более 4294967295 элементов (что в любом случае маловероятно), у вас возникнут проблемы, если вы захотите прочитать десериализовать этот файл из 32-битной программы. Но в 32-битной программе CArray
все равно не может хранить более 4294967295.
Короче говоря, вам не нужно делать ничего особенного, просто сериализуйте/десериализуйте свои данные.
Не уверен, если я правильно понимаю. Обновление не проблема, но что если мы сериализуем (записываем) наши данные в компиляции x64, а затем десериализуем (читаем) обратно в компиляцию x32. Мы не должны беспокоиться об этом тоже?
@TomTom нет, это совсем не проблема. Прочтите также ответ IInspectable, который является гораздо более подробным.
Хранение и извлечение количества элементов для экземпляров CArray
реализованы в CArchive::WriteCount и CArchive::ReadCount соответственно.
Они записывают и считывают 16-битное (WORD
), 32-битное (DWORD
) или 64-битное (на 64-битных платформах, DWORD_PTR
) значение в поток или из него. При написании используется следующий алгоритм:
0xFFFF
, запишите количество предметов в виде 16-битного WORD
значения.(WORD)0xFFFF
), а затем
DWORD
)0xFFFF'FFFF
, запишите количество элементов в виде 32-битного DWORD
значения.
(DWORD)0xFFFFFFFF
), за которым следует количество элементов в виде 64-битного значения (DWORD_PTR
).Схема потока представлена в следующей таблице в зависимости от количества элементов в CArray
(где ❌ обозначает значение, отсутствующее в потоке):
WORD
DWORD
DWORD_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 Это объективно дольше. Насчет "лучше" не уверен. Некоторые люди всегда спешат, и слово «короткий» может больше соответствовать продолжительности их внимания.
Компиляция/совместимость зависит от платформы, но нет, реализация не "универсальна", если хотите. Исходный код MFC открыт (в C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.34.31933\atlmfc\src\mfc в последних сборках Visual Studio), поэтому вы можете проверить все, что вы нужно самому. Вот он онлайн github.com/pixelspark/corespark/blob/master/Libraries/atlmfc/…