TN002. Формат данных постоянного объекта
В этом примечании описываются подпрограммы MFC, поддерживающие постоянные объекты C++ и формат данных объекта при его хранении в файле. Это относится только к классам с DECLARE_SERIAL и макросами IMPLEMENT_SERIAL .
Проблема
Реализация MFC для постоянных хранилищ данных для многих объектов в одной непрерывной части файла. Метод объекта преобразует данные объекта Serialize
в компактный двоичный формат.
Реализация гарантирует, что все данные сохраняются в одном формате с помощью класса CArchive. Он использует объект в CArchive
качестве переводчика. Этот объект сохраняется с момента его создания до вызова CArchive::Close. Этот метод может вызываться либо явным образом программистом, либо неявно деструктором, когда программа выходит из область, содержащей объектCArchive
.
Это примечание описывает реализацию CArchive
элементов CArchive::ReadObject и CArchive::WriteObject. Вы найдете код для этих функций в Arcobj.cpp и основную реализацию в CArchive
Arccore.cpp. Пользовательский код не вызывает ReadObject
и WriteObject
напрямую. Вместо этого эти объекты используются операторами безопасной вставки и извлечения, созданными автоматически DECLARE_SERIAL и макросами IMPLEMENT_SERIAL. В следующем коде показано, как WriteObject
и ReadObject
неявно вызываться:
class CMyObject : public CObject
{
DECLARE_SERIAL(CMyObject)
};
IMPLEMENT_SERIAL(CMyObj, CObject, 1)
// example usage (ar is a CArchive&)
CMyObject* pObj;
CArchive& ar;
ar <<pObj; // calls ar.WriteObject(pObj)
ar>> pObj; // calls ar.ReadObject(RUNTIME_CLASS(CObj))
Сохранение объектов в хранилище (CArchive::WriteObject)
Метод CArchive::WriteObject
записывает данные заголовка, используемые для восстановления объекта. Эти данные состоят из двух частей: типа объекта и состояния объекта. Этот метод также отвечает за сохранение удостоверения записываемого объекта, чтобы сохранить только одну копию независимо от количества указателей на этот объект (включая циклические указатели).
Сохранение (вставка) и восстановление (извлечение) объектов зависит от нескольких "констант манифеста". Это значения, которые хранятся в двоичном файле и предоставляют важные сведения для архива (обратите внимание, что префикс W указывает 16-разрядные количества):
Тег | Description |
---|---|
wNullTag | Используется для указателей объектов NULL (0). |
wNewClassTag | Указывает, что следующее описание класса является новым для этого контекста архива (-1). |
wOldClassTag | Указывает класс считываемого объекта в этом контексте (0x8000). |
При хранении объектов архив сохраняет CMapPtrToPtr ( m_pStoreMap), который является сопоставлением из хранимого объекта с 32-разрядным постоянным идентификатором (PID). PiD назначается каждому уникальному объекту и каждому уникальному имени класса, сохраненного в контексте архива. Эти идентификаторы передаются последовательно, начиная с 1. Эти идентификаторы не имеют значения за пределами область архива, и, в частности, не следует путать с номерами записей или другими элементами удостоверения.
CArchive
В классе ИДЕНТИФИКАТОРы 32-разрядные, но они записываются как 16-разрядные, если они больше, чем 0x7FFE. Большие пин-коды записываются как 0x7FFF за которым следует 32-разрядный ИДЕНТИФИКАТОР. Это обеспечивает совместимость с проектами, созданными в более ранних версиях.
Когда запрос выполняется для сохранения объекта в архив (обычно с помощью глобального оператора вставки), проверка выполняется для указателя CObject NULL. Если указатель имеет значение NULL, wNullTag вставляется в архивный поток.
Если указатель не имеет значения NULL и может быть сериализован (класс является классомDECLARE_SERIAL
), код проверка m_pStoreMap, чтобы узнать, сохранен ли объект уже. Если он имеется, код вставляет 32-разрядный PID, связанный с этим объектом, в архивный поток.
Если объект не был сохранен до этого, существует два способа рассмотреть: как объект, так и точный тип (то есть класс) объекта являются новыми для этого архивного контекста, или объект имеет точный тип, который уже видел. Чтобы определить, был ли замечен тип, код запрашивает m_pStoreMap для объекта CRuntimeClass , соответствующего объекту, связанному CRuntimeClass
с сохраненным объектом. Если имеется совпадение, WriteObject
вставляет тег, который является битовой OR
частью wOldClassTag и этим индексом. Если контекст CRuntimeClass
архива не является новым, WriteObject
он назначает новый ИДЕНТИФИКАТОР этому классу и вставляет его в архив перед значением wNewClassTag .
Затем дескриптор CRuntimeClass::Store
этого класса вставляется в архив с помощью метода. CRuntimeClass::Store
вставляет номер схемы класса (см. ниже) и текстовое имя ASCII класса. Обратите внимание, что использование текстового имени ASCII не гарантирует уникальность архива в приложениях. Поэтому следует пометить файлы данных, чтобы предотвратить повреждение. После вставки сведений о классе архив помещает объект в m_pStoreMap , а затем вызывает метод для вставки данных, относящихся к классу Serialize
. Поместите объект в m_pStoreMap перед вызовом Serialize
, чтобы предотвратить сохранение нескольких копий объекта в хранилище.
При возвращении к исходному вызывающому объекту (как правило, корне сети объектов) необходимо вызвать CArchive::Close. Если вы планируете выполнять другие операции CFile, необходимо вызвать CArchive
метод Flush , чтобы предотвратить повреждение архива.
Примечание.
Эта реализация накладывает жесткое ограничение 0x3FFFFFFE индексов для каждого контекста архива. Это число представляет максимальное количество уникальных объектов и классов, которые можно сохранить в одном архиве, но один файл диска может иметь неограниченное количество контекстов архива.
Загрузка объектов из Магазина (CArchive::ReadObject)
Загрузка (извлечение) объектов использует CArchive::ReadObject
метод и является обратным WriteObject
. Как и в случае WriteObject
с кодом пользователя, ReadObject
не вызывается напрямую; код пользователя должен вызывать оператор безопасного извлечения типа, вызывающий ReadObject
ожидаемый CRuntimeClass
код. Это проверяет целостность типа операции извлечения.
WriteObject
Так как реализация, назначенная увеличением идентификаторов идентификаторов, начиная с 1 (0 предопределена как объект NULL), ReadObject
реализация может использовать массив для поддержания состояния контекста архива. Если piD считывается из хранилища, если идентификатор PID больше текущей верхней границы m_pLoadArray, знает, ReadObject
что новый объект (или описание класса) следует.
Номера схемы
Номер схемы, назначенный классу при IMPLEMENT_SERIAL
обнаружении метода класса, является "версия" реализации класса. Схема относится к реализации класса, а не к количеству постоянных объектов (обычно называется версией объекта).
Если вы планируете поддерживать несколько различных реализаций одного класса с течением времени, добавив схему при пересмотре реализации метода объекта Serialize
, вы сможете написать код, который может загружать объекты, хранящиеся с помощью более старых версий реализации.
Метод CArchive::ReadObject
выдает CArchiveException при обнаружении номера схемы в постоянном хранилище, которое отличается от номера схемы описания класса в памяти. Это не легко восстановить из этого исключения.
Вы можете использовать VERSIONABLE_SCHEMA
в сочетании с (побитовой ИЛИ) версией схемы, чтобы сохранить это исключение от возникновения. С помощью VERSIONABLE_SCHEMA
кода можно выполнить соответствующее действие в своей Serialize
функции, проверка возвращаемое значение из CArchive::GetObjectSchema.
Вызов сериализации напрямую
Во многих случаях затраты на общую схему WriteObject
архива объектов не ReadObject
требуется. Это распространенный случай сериализации данных в CDocument. В этом случае Serialize
метод CDocument
вызывается напрямую, а не с операторами извлечения или вставки. Содержимое документа может в свою очередь использовать более общую схему архивирования объектов.
Вызов Serialize
напрямую имеет следующие преимущества и недостатки:
Дополнительные байты не добавляются в архив до или после сериализации объекта. Это не только делает сохраненные данные меньше, но позволяет реализовать
Serialize
подпрограммы, которые могут обрабатывать любые форматы файлов.MFC настраивается таким образом, что
WriteObject
реализации иReadObject
связанные коллекции не будут связаны с приложением, если вам не нужна более общая схема архива объектов для какой-то другой цели.Код не должен восстанавливаться из старых чисел схемы. Это делает код сериализации документов ответственным за код кодирования чисел схемы, номера версий формата файла или любые номера, которые вы используете в начале файлов данных.
Любой объект, сериализованный с прямым вызовом
Serialize
, не должен использоватьCArchive::GetObjectSchema
или должен обрабатывать возвращаемое значение (UINT)-1, указывающее, что версия неизвестна.
Так как Serialize
он вызывается непосредственно в документе, обычно не удается архивировать ссылки на родительский документ. Эти объекты должны быть явно заданы указателем на документ контейнера или использовать функцию CArchive::MapObject для сопоставления CDocument
указателя с ИДЕНТИФИКАТОРом перед архивированием этих обратных указателей.
Как отмечалось ранее, вы должны закодировать сведения о версии и классе самостоятельно при вызове Serialize
напрямую, что позволяет изменить формат позже, сохраняя обратную совместимость со старыми файлами. Функцию CArchive::SerializeClass
можно вызывать явно перед непосредственной сериализацией объекта или перед вызовом базового класса.
См. также
Технические примечания по номеру
Технические примечания по категории