Сериализация данных производится с помощью операторов operator<< и operator>> , совсем как в случае с классом iostream . Разница в том, что CArchive подразумевает только двоичный формат данных. Подобно iostream , в CArchive реализованы операторы для чтения и записи фундаментальных типов данных, таких как long и char . Отсутствие типа данных int упрощает переносимость между 16– и 32-битными платформами. Встроенные операторы также реализуют перестановку байтов для типов, которые это поддерживают. (За дополнительной информацией о совместимости между платформами с прямой и обратной записью байтов [Little-Endian и Big-Endian] обратитесь к книге "Endian-Neutral Software," by James R. Gillig, DDJ, October/November 1994).
Реализация сериализации в MFC выглядела очевидной и неинтересной до того момента, когда мне понадобилось создать несколько типов документов в одном приложении. Я заметил, что когда я открывал в программе файл, MFC корректно создавала объект документа нужного типа и вызывала соответствующую функцию Serialize() . Это происходило, несмотря на то, что я не написал ни строчки кода, чтобы помочь MFC в создании документов. По крайней мере, я так считал:
Проблемы, всюду проблемы
Чтобы создавать документ или любой другой вид объектов "на лету", MFC должна решить три проблемы:
Проблема 1. По мере необходимости должны создаваться объекты произвольных типов, но оператор new может работать только с явно указанным типом, поэтому для CObject нужно реализовать некое подобие "виртуальных конструкторов".
Проблема 2. Разработчики должны иметь возможность легко "обучать" CObject создавать новые типы классов. В идеале, это должно делаться в определении и/или реализации класса.
Проблема 3. Необходим механизм сопоставления для создания объекта нужного типа по информации, прочитанной из файла. Этот механизм не может быть жестко зашит в MFC, потому что разработчики все время добавляют новые типы данных.
Как будет продемонстрировано далее, MFC элегантно решает эти проблемы, используя реестр с автоматической регистрацией типов [ здесь и далее под реестром понимается программная структура, а не реестр WIndows – прим.пер. ] и реализуя виртуальные конструкторы на основе зарегистрированных типов. Идентификация типов во время исполнения (RTTI) библиотеки MFC – вот краеугольный камень этой архитектуры.
Реестр типов
Чтобы осуществить идентификацию объектов во время выполнения, MFC создает в приложении реестр классов, унаследованных от CObject . Этот реестр никак не связан с OLE-реестром, но их концепции схожи. Реестр типов представляет собой связанный список структур CRuntimeClass , в котором каждая структура описывает один класс-наследник CObject . На листинге 1 показано внутреннее устройство структуры CRuntimeClass .
Листинг 1
struct CRuntimeClass {
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // номер схемы загруженного класса
void (PASCAL* m_pfnConstruct)(void* p); // NULL => abstract class
CRuntimeClass* m_pBaseClass;
// Operations
CObject* CreateObject();
// Implementation
BOOL ConstructObject(void* pThis);
void Store(CArchive& ar);
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// объекты CRuntimeClass, связанные в простой список
CRuntimeClass* m_pNextClass;// список зарегистрированных классов
};
Весь фокус в том, что типы из этого реестра не прописаны ни в одной таблице. Первый ключ к разгадке этого феномена находится в начале файла SCRIBDOC.H из MFC-примера "Scribble". Начало объявления класса выглядит так, как показано в примере 1(a).
Пример 1: (a) Начало объявления класса; (b) после обработки препроцессором макрос DECLARE_DYNCREATE разворачивается в несколько новых членов класса.
(a)
class CScribDoc : public CDocument {
protected:
// создавать только при сериализации
CScribDoc();
DECLARE_DYNCREATE(CScribDoc)
...
};
(b)
protected:
static CRuntimeClass* __stdcall _GetBaseClass();
public:
static CRuntimeClass classCScribDoc;
virtual CRuntimeClass* GetRuntimeClass() const;
static void__stdcall Construct(void* p);
В документации сказано, что макрос DECLARE_DYNCREATE позволяет классам-наследникам CObject создаваться динамически во время выполнения. Хотя это определение абсолютно верно, то, что происходит внутри этого макроса, гораздо интереснее. После обработки препроцессором макрос DECLARE_DYNCREATE разворачивается в несколько новых членов класса, как показано в примере 1(b) (Все примеры взяты из MFC 3.1 и Visual C++ 2.1. Я выровнял код, сгенерированный препроцессором, для повышения читабельности).
Читать дальше