Приветствую!
Случилось так, что мне пришлось потратить целый рабочий день на поиск одного решения в области сериализации; готов поделиться.
Как я уже отмечал недавно, при сериализации данных нужно решать проблему совместимости старых структур сохраняемых данных, т.е. если вы добавили новое поле в какой-то класс, подлежащий сериализации, будьте добры, обеспечьте чтение этого класса, ранее сохраненного без этого поля.
Способ, который я приводил (ReadClass/WriteClass), вполне хорош. Но закавыка-то в том, что в предыдущей версии я по какой-то причине (просто по неопытности) сохранял без WriteClass! А имплементация сериализации была описана как IMPLEMENT_SERIAL(MyClass, CObject, 0)
Итак, подобным образом сериализованный класс надо было успешно прочитать. Понятное дело, ReadClass на такую сериализацию вызывает CArchiveException с кодом CArchiveException::badIndex (=5, для справки)
Казалось бы, лови CArchiveException и обрабатывай себе, но не тут-то было! Вроде незначительная проблема: указатель архива продвигается, и невозможно уже прочитать данные из-за смещения. Никакого средства для возврата указателя на место для CArchive не существует! (Я бы мог попросить уважаемых читателей порыться, но заранее заявляю: бессмысленно! Говорите, можно манипулировать с функцией Seek принадлежащего
CArchive классу CFile? – пробовал, не работает.)
По счастью, нашлась недокументированная возможность. Выяснилось, и это очень важный нюанс, который не разъяснен в MSDN: У функции CReadClass есть 3 параметра, 2 последних имеют умолчание NULL:
CRuntimeClass* ReadClass(const CRuntimeClass* pClassRefRequested = NULL, UINT* pSchema = NULL, DWORD* obTag = NULL);
throw CArchiveException;
throw CNotSupportedException;
Первый параметр – RuntimeClass для проверки на соответствие загружаемого класса.
Второй параметр – поинтер на UINT – версию сериализуемого класса.
Третий параметр, и это самое интересное (кстати, в MSDN записано, что, он предназначен для внутреннего использования в функции ReadClass и обычно задается как NULL) – если не задан нулем, возвращает в младших 2-х байтах значение, сериализованное из архива, а CArchiveException при этом не вырабатывается! Версия при этом не заполняется.
Отсюда решение, которое проиллюстрировано во фрагменте кода:
#define BASE_DATA_VERSION 0x100
IMPLEMENT_SERIAL(MyClass, CObject, VERSIONABLE_SCHEMA | BASE_DATA_VERSION)
void MyClass::Serialize(CArchive& ar) {
UINT Version=NULL;
CObject::Serialize(ar);
DWORD Tag;
if(ar.IsLoading) {
TRY {
ar.ReadClass(RUNTIME_CLASS(MyClass), &Version, &Tag);
if (Version == BASE_DATA_VERSION)
ar >> dwValue; // описано в классе DWORD dwValue;
else {
WORD HighW;
ar >> HighW;
dwValue = MAKELONG(Tag, HighW);
}
ar.Read( title, sizeof(title)); // описано в классе
// char title[LEN_TITLE];
ar>> val1;
ar>> val2; // нормальная сериализация
…