TN002: Persistentes Objektdatenformat

In diesem Hinweis werden die MFC-Routinen beschrieben, die persistente C++-Objekte und das Format der Objektdaten unterstützen, wenn sie in einer Datei gespeichert werden. Dies gilt nur für Klassen mit den makros DECLARE_SERIAL und IMPLEMENT_SERIAL .

Problemstellung

Die MFC-Implementierung für persistente Datenspeicherdaten für viele Objekte in einem einzigen zusammenhängenden Teil einer Datei. Die Methode des Serialize Objekts übersetzt die Daten des Objekts in ein kompaktes Binärformat.

Die Implementierung garantiert, dass alle Daten mithilfe der CArchive-Klasse im selben Format gespeichert werden. Es verwendet ein CArchive Objekt als Übersetzer. Dieses Objekt wird ab dem Zeitpunkt der Erstellung beibehalten, bis Sie CArchive::Close aufrufen. Diese Methode kann entweder explizit vom Programmierer oder implizit vom Destruktor aufgerufen werden, wenn das Programm den Bereich beendet, der die CArchiveDatei enthält.

In diesem Hinweis wird die Implementierung der CArchive Member CArchive::ReadObject und CArchive::WriteObject beschrieben. Sie finden den Code für diese Funktionen in Arcobj.cpp und die Standard Implementierung für CArchive Arccore.cpp. Der Benutzercode ruft nicht direkt und WriteObject nicht aufReadObject. Stattdessen werden diese Objekte von klassenspezifischen typsicheren Einfüge- und Extraktionsoperatoren verwendet, die automatisch von den DECLARE_SERIAL und IMPLEMENT_SERIAL Makros generiert werden. Der folgende Code zeigt, wie WriteObject und ReadObject implizit aufgerufen wird:

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))

Speichern von Objekten im Speicher (CArchive::WriteObject)

Die Methode CArchive::WriteObject schreibt Kopfzeilendaten, die zum Rekonstruieren des Objekts verwendet werden. Diese Daten bestehen aus zwei Teilen: dem Typ des Objekts und dem Status des Objekts. Diese Methode ist auch dafür verantwortlich, die Identität des zu schreibenden Objekts zu Standard, sodass nur eine einzelne Kopie gespeichert wird, unabhängig von der Anzahl der Zeiger auf dieses Objekt (einschließlich Zirkelzeiger).

Das Speichern (Einfügen) und Wiederherstellen (Extrahieren) von Objekten basiert auf mehreren "Manifestkonstanten". Dies sind Werte, die in binär gespeichert sind und wichtige Informationen für das Archiv bereitstellen (beachten Sie, dass das Präfix "w" 16-Bit-Mengen angibt):

Tag Beschreibung
wNullTag Wird für NULL-Objektzeiger (0) verwendet.
wNewClassTag Gibt die folgende Klassenbeschreibung an, die in diesem Archivkontext (-1) neu ist.
wOldClassTag Gibt an, dass die Klasse des gelesenen Objekts in diesem Kontext (0x8000) angezeigt wurde.

Beim Speichern von Objekten Standard archiviert ein CMapPtrToPtrToPtr (das m_pStoreMap), bei dem es sich um eine Zuordnung von einem gespeicherten Objekt zu einem 32-Bit-persistenten Bezeichner (PID) handelt. Jedem eindeutigen Objekt und jedem eindeutigen Klassennamen, der im Kontext des Archivs gespeichert wird, wird eine PID zugewiesen. Diese PIDs werden sequenziell ab 1 weitergegeben. Diese PIDs haben keine Bedeutung außerhalb des Archivbereichs und sind insbesondere nicht mit Datensatznummern oder anderen Identitätselementen zu verwechseln.

In der CArchive Klasse sind PIDs 32-Bit, aber sie werden als 16-Bit-Version geschrieben, es sei denn, sie sind größer als 0x7FFE. Große PIDs werden als 0x7FFF gefolgt von der 32-Bit-PID geschrieben. Dadurch Standard kompatibilität mit Projekten, die in früheren Versionen erstellt wurden.

Wenn eine Anforderung zum Speichern eines Objekts in einem Archiv (in der Regel mithilfe des globalen Einfügeoperators) erfolgt, wird eine Überprüfung für einen NULL-CObject-Zeiger durchgeführt. Wenn der Zeiger NULL ist, wird das wNullTag in den Archivdatenstrom eingefügt.

Wenn der Zeiger nicht NULL ist und serialisiert werden kann (die Klasse ist eine DECLARE_SERIAL Klasse), überprüft der Code die m_pStoreMap , um festzustellen, ob das Objekt bereits gespeichert wurde. Falls ja, fügt der Code die diesem Objekt zugeordnete 32-Bit-PID in den Archivdatenstrom ein.

Wenn das Objekt noch nicht gespeichert wurde, gibt es zwei Möglichkeiten zu berücksichtigen: entweder das Objekt und der genaue Typ (d. h. die Klasse) des Objekts sind in diesem Archivkontext neu, oder das Objekt ist bereits ein exakter Typ. Um zu ermitteln, ob der Typ angezeigt wurde, fragt der Code die m_pStoreMap für ein CRuntimeClass-Objekt ab, das dem CRuntimeClass objekt zugeordnet ist, das gespeichert wird. Wenn eine Übereinstimmung vorhanden ist, wird ein Tag eingefügt, WriteObject das bitweise OR von wOldClassTag und diesem Index ist. Wenn der CRuntimeClass Archivkontext neu ist, WriteObject weist sie dieser Klasse eine neue PID zu und fügt sie in das Archiv ein, dem der Wert "wNewClassTag " vorangestellt ist.

Der Deskriptor für diese Klasse wird dann mithilfe der CRuntimeClass::Store Methode in das Archiv eingefügt. CRuntimeClass::Store fügt die Schemanummer der Klasse (siehe unten) und den ASCII-Textnamen der Klasse ein. Beachten Sie, dass die Verwendung des ASCII-Textnamens nicht die Eindeutigkeit des Archivs in allen Anwendungen garantiert. Daher sollten Sie Ihre Datendateien markieren, um Beschädigungen zu verhindern. Nach dem Einfügen der Klasseninformationen platziert das Archiv das Objekt in die m_pStoreMap und ruft dann die Serialize Methode auf, um klassenspezifische Daten einzufügen. Wenn Sie das Objekt in die m_pStoreMap setzen, bevor Sie aufrufen Serialize , wird verhindert, dass mehrere Kopien des Objekts im Speicher gespeichert werden.

Wenn Sie zum anfänglichen Aufrufer zurückkehren (in der Regel das Stammverzeichnis des Netzwerks von Objekten), müssen Sie CArchive::Close aufrufen. Wenn Sie beabsichtigen, andere CFile-Vorgängeauszuführen, müssen Sie die CArchive Methode Flush aufrufen, um Beschädigungen des Archivs zu verhindern.

Hinweis

Diese Implementierung legt eine harte Grenze von 0x3FFFFFFE Indizes pro Archivkontext fest. Diese Zahl stellt die maximale Anzahl eindeutiger Objekte und Klassen dar, die in einem einzelnen Archiv gespeichert werden können, aber eine einzelne Datenträgerdatei kann eine unbegrenzte Anzahl von Archivkontexten aufweisen.

Laden von Objekten aus dem Store (CArchive::ReadObject)

Das Laden (Extrahieren) von Objekten verwendet die CArchive::ReadObject Methode und umgekehrt von WriteObject. Wie bei WriteObject, ReadObject wird nicht direkt von Benutzercode aufgerufen. Benutzercode sollte den typsicheren Extraktionsoperator aufrufen, der mit dem erwarteten Aufruf aufruft ReadObject CRuntimeClass. Dadurch wird die Typintegrität des Extraktvorgangs sichergestellt.

Da die WriteObject Implementierung steigende PIDs zugewiesen hat, beginnend mit 1 (0 ist als NULL-Objekt vordefiniert), kann die ReadObject Implementierung ein Array verwenden, um den Status des Archivkontexts zu Standard. Wenn eine PID aus dem Speicher gelesen wird, ist die PID größer als die aktuelle obergrenze des m_pLoadArray, weiß, ReadObject dass ein neues Objekt (oder eine Klassenbeschreibung) folgt.

Schemanummern

Die Schemanummer, die der Klasse zugewiesen ist, wenn die IMPLEMENT_SERIAL Methode der Klasse gefunden wird, ist die "Version" der Klassenimplementierung. Das Schema bezieht sich auf die Implementierung der Klasse, nicht auf die Häufigkeit, mit der ein bestimmtes Objekt dauerhaft gemacht wurde (in der Regel als Objektversion bezeichnet).

Wenn Sie beabsichtigen, mehrere verschiedene Implementierungen derselben Klasse im Laufe der Zeit zu Standard, können Sie beim Überarbeiten der Methodenimplementierung des Objekts Serialize Code schreiben, mit dem Objekte geladen werden können, die mit älteren Versionen der Implementierung gespeichert werden.

Die CArchive::ReadObject Methode löst eine CArchiveException aus, wenn eine Schemanummer im persistenten Speicher auftritt, die sich von der Schemanummer der Klassenbeschreibung im Arbeitsspeicher unterscheidet. Es ist nicht einfach, aus dieser Ausnahme wiederherzustellen.

Sie können ihre Schemaversion in Kombination mit (bitweise ODER) verwendenVERSIONABLE_SCHEMA, damit diese Ausnahme nicht ausgelöst wird. Mithilfe von VERSIONABLE_SCHEMA kann ihr Code die entsprechende Aktion in seiner Serialize Funktion ausführen, indem der Rückgabewert von CArchive::GetObjectSchema überprüft wird.

Direktes Serialisieren von Anrufen

In vielen Fällen ist der Aufwand des allgemeinen Objektarchivschemas WriteObject und ReadObject nicht erforderlich. Dies ist der häufige Fall der Serialisierung der Daten in ein CDocument. In diesem Fall wird die Serialize Methode der CDocument Methode direkt aufgerufen, nicht mit den Extrakt- oder Einfügeoperatoren. Der Inhalt des Dokuments kann wiederum das allgemeinere Objektarchivschema verwenden.

Das Direkte Anrufen Serialize hat die folgenden Vor- und Nachteile:

  • Dem Archiv werden vor oder nach der Serialisierung des Objekts keine zusätzlichen Bytes hinzugefügt. Dadurch werden die gespeicherten Daten nicht nur kleiner, sondern Sie können Routinen implementieren Serialize , die alle Dateiformate verarbeiten können.

  • Der MFC ist so abgestimmt, dass die WriteObject Implementierungen und ReadObject verwandten Auflistungen nicht mit Ihrer Anwendung verknüpft werden, es sei denn, Sie benötigen das allgemeinere Objektarchivschema für einen anderen Zweck.

  • Ihr Code muss nicht aus alten Schemanummern wiederhergestellt werden. Dadurch ist Ihr Dokument serialisierungscode verantwortlich für die Codierung von Schemanummern, Dateiformatversionsnummern oder für die Identifizierung von Nummern, die Sie am Anfang Ihrer Datendateien verwenden.

  • Jedes Objekt, das mit einem direkten Aufruf Serialize serialisiert wird, darf keinen Rückgabewert von (UINT)-1 verwenden CArchive::GetObjectSchema oder verarbeiten, der angibt, dass die Version unbekannt war.

Da Serialize direkt in Ihrem Dokument aufgerufen wird, ist es in der Regel für die Unterobjekte des Dokuments nicht möglich, Verweise auf das übergeordnete Dokument zu archivieren. Diese Objekte müssen explizit einem Zeiger auf das Containerdokument zugewiesen werden, oder Sie müssen die CArchive::MapObject-Funktion verwenden, um den CDocument Zeiger einer PID zuzuordnen, bevor diese Zurückzeiger archiviert werden.

Wie bereits erwähnt, sollten Sie die Versions- und Klasseninformationen selbst codieren, wenn Sie direkt aufrufenSerialize, sodass Sie das Format später ändern können, während sie weiterhin die Abwärtskompatibilität mit älteren Dateien Standard. Die CArchive::SerializeClass Funktion kann explizit aufgerufen werden, bevor ein Objekt direkt serialisiert wird oder bevor eine Basisklasse aufgerufen wird.

Siehe auch

Technische Hinweise – nach Nummern geordnet
Technische Hinweise – nach Kategorien geordnet