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 можно вызывать явно перед непосредственной сериализацией объекта или перед вызовом базового класса.

См. также

Технические примечания по номеру
Технические примечания по категории