Partager via


TN002 : format des données d'objets persistants

Cette note décrit les routines MFC qui prennent en charge les objets C++ persistants et le format des données d’objet lorsqu’elles sont stockées dans un fichier. Cela s’applique uniquement aux classes avec les macros DECLARE_SERIAL et IMPLEMENT_SERIAL .

Le problème

Implémentation MFC pour les données persistantes stocke les données de nombreux objets dans une partie contiguë unique d’un fichier. La méthode de l’objet traduit les données de Serialize l’objet dans un format binaire compact.

L’implémentation garantit que toutes les données sont enregistrées dans le même format à l’aide de la classe CArchive. Il utilise un CArchive objet comme traducteur. Cet objet persiste à partir du moment où il est créé jusqu’à ce que vous appeliez CArchive ::Close. Cette méthode peut être appelée explicitement par le programmeur ou implicitement par le destructeur lorsque le programme quitte l’étendue qui contient le CArchive.

Cette note décrit l’implémentation des CArchive membres CArchive ::ReadObject et CArchive ::WriteObject. Vous trouverez le code de ces fonctions dans Arcobj.cpp et l’implémentation principale dans CArchive Arccore.cpp. Le code utilisateur n’appelle pas et WriteObject n’appelle ReadObject pas directement. Au lieu de cela, ces objets sont utilisés par des opérateurs d’insertion et d’extraction de type spécifiques à la classe qui sont générés automatiquement par les macros DECLARE_SERIAL et IMPLEMENT_SERIAL. Le code suivant montre comment WriteObject et ReadObject sont appelés implicitement :

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

Enregistrement d’objets dans le Windows Store (CArchive ::WriteObject)

La méthode CArchive::WriteObject écrit les données d’en-tête utilisées pour reconstruire l’objet. Ces données se composent de deux parties : le type de l’objet et l’état de l’objet. Cette méthode est également responsable de la maintenance de l’identité de l’objet en cours d’écriture, afin qu’une seule copie soit enregistrée, quel que soit le nombre de pointeurs vers cet objet (y compris les pointeurs circulaires).

L’enregistrement (insertion) et la restauration (extraction) des objets reposent sur plusieurs « constantes manifestes ». Il s’agit de valeurs stockées dans un fichier binaire et qui fournissent des informations importantes à l’archive (notez que le préfixe « w » indique des quantités 16 bits) :

Balise Description
wNullTag Utilisé pour les pointeurs d’objet NULL (0).
wNewClassTag Indique la description de classe qui suit est nouvelle dans ce contexte d’archive (-1).
wOldClassTag Indique que la classe de l’objet lu a été vue dans ce contexte (0x8000).

Lors du stockage d’objets, l’archive conserve un CMapPtrToPtr (le m_pStoreMap) qui est un mappage d’un objet stocké à un identificateur persistant 32 bits (PID). Un PID est affecté à chaque objet unique et à chaque nom de classe unique enregistré dans le contexte de l’archive. Ces PID sont distribués séquentiellement à partir de 1. Ces PID n’ont aucune importance en dehors de l’étendue de l’archive et, en particulier, ne doivent pas être confondus avec les numéros d’enregistrement ou d’autres éléments d’identité.

Dans la CArchive classe, les PID sont 32 bits, mais ils sont écrits en tant que 16 bits, sauf si elles sont supérieures à 0x7FFE. Les piD volumineux sont écrits en tant que 0x7FFF suivis du PID 32 bits. Cela maintient la compatibilité avec les projets créés dans les versions antérieures.

Lorsqu’une demande est effectuée pour enregistrer un objet dans une archive (généralement à l’aide de l’opérateur d’insertion globale), un case activée est effectué pour un pointeur CObject NULL. Si le pointeur a la valeur NULL, le wNullTag est inséré dans le flux d’archivage.

Si le pointeur n’est pas NULL et peut être sérialisé (la classe est une DECLARE_SERIAL classe), le code case activée l’m_pStoreMap pour voir si l’objet a déjà été enregistré. S’il en a, le code insère le PID 32 bits associé à cet objet dans le flux d’archivage.

Si l’objet n’a pas été enregistré précédemment, il existe deux possibilités à prendre en compte : l’objet et le type exact (c’est-à-dire la classe) de l’objet sont nouveaux dans ce contexte d’archive, ou l’objet est d’un type exact déjà vu. Pour déterminer si le type a été vu, le code interroge l’m_pStoreMap pour un objet CRuntimeClass qui correspond à l’objet CRuntimeClass associé à l’objet enregistré. S’il existe une correspondance, WriteObject insère une balise qui est au niveau OR du bit de wOldClassTag et de cet index. Si le CRuntimeClass nouveau contexte d’archive est nouveau, WriteObject attribue un nouveau PID à cette classe et l’insère dans l’archive, précédé de la valeur wNewClassTag .

Le descripteur de cette classe est ensuite inséré dans l’archive à l’aide de la CRuntimeClass::Store méthode. CRuntimeClass::Store insère le numéro de schéma de la classe (voir ci-dessous) et le nom de texte ASCII de la classe. Notez que l’utilisation du nom de texte ASCII ne garantit pas l’unicité de l’archive entre les applications. Par conséquent, vous devez étiqueter vos fichiers de données pour éviter toute altération. Après l’insertion des informations de classe, l’archive place l’objet dans la m_pStoreMap , puis appelle la Serialize méthode pour insérer des données spécifiques à la classe. Placer l’objet dans la m_pStoreMap avant d’appeler Serialize empêche l’enregistrement de plusieurs copies de l’objet dans le magasin.

Lorsque vous revenez à l’appelant initial (généralement la racine du réseau d’objets), vous devez appeler CArchive ::Close. Si vous envisagez d’effectuer d’autres opérations CFile, vous devez appeler la CArchive méthode Flush pour empêcher l’altération de l’archive.

Remarque

Cette implémentation impose une limite stricte de 0x3FFFFFFE index par contexte d’archivage. Ce nombre représente le nombre maximal d’objets et de classes uniques qui peuvent être enregistrés dans une archive unique, mais un seul fichier disque peut avoir un nombre illimité de contextes d’archivage.

Chargement d’objets à partir du Windows Store (CArchive ::ReadObject)

Le chargement (extraction) d’objets utilise la CArchive::ReadObject méthode et est l’inverse de WriteObject. Comme avec WriteObject, ReadObject n’est pas appelé directement par le code utilisateur ; le code utilisateur doit appeler l’opérateur d’extraction de type sécurisé qui appelle ReadObject avec le code attendu CRuntimeClass. Cela garantit l’intégrité du type de l’opération d’extraction.

Étant donné que l’implémentation WriteObject a affecté des PID croissants, à compter de 1 (0 est prédéfini en tant qu’objet NULL), l’implémentation ReadObject peut utiliser un tableau pour maintenir l’état du contexte d’archivage. Lorsqu’un PID est lu à partir du magasin, si le PID est supérieur à la limite supérieure actuelle du m_pLoadArray, ReadObject sait qu’un nouvel objet (ou description de classe) suit.

Numéros de schéma

Le numéro de schéma, qui est affecté à la classe lorsque la IMPLEMENT_SERIAL méthode de la classe est rencontrée, est la « version » de l’implémentation de classe. Le schéma fait référence à l’implémentation de la classe, et non au nombre de fois où un objet donné a été rendu persistant (généralement appelé version de l’objet).

Si vous envisagez de conserver plusieurs implémentations différentes de la même classe au fil du temps, l’incrémentation du schéma lorsque vous modifiez l’implémentation de la méthode de Serialize votre objet vous permet d’écrire du code qui peut charger des objets stockés à l’aide de versions antérieures de l’implémentation.

La CArchive::ReadObject méthode lève une exception CArchiveException lorsqu’elle rencontre un numéro de schéma dans le magasin persistant qui diffère du numéro de schéma de la description de classe en mémoire. Il n’est pas facile de récupérer à partir de cette exception.

Vous pouvez utiliser VERSIONABLE_SCHEMA combiné avec (or au niveau du bit) votre version de schéma pour empêcher cette exception d’être levée. En utilisant VERSIONABLE_SCHEMA, votre code peut prendre l’action appropriée dans sa Serialize fonction en case activée la valeur de retour de CArchive ::GetObjectSchema.

Appel de sérialiser directement

Dans de nombreux cas, la surcharge du schéma d’archive d’objet général et WriteObjectReadObject n’est pas nécessaire. Il s’agit du cas courant de sérialisation des données dans un CDocument. Dans ce cas, la Serialize méthode de l’objet CDocument est appelée directement, et non avec les opérateurs d’extraction ou d’insertion. Le contenu du document peut à son tour utiliser le schéma d’archive d’objet plus général.

L’appel Serialize présente directement les avantages et inconvénients suivants :

  • Aucun octet supplémentaire n’est ajouté à l’archive avant ou après la sérialisation de l’objet. Cela rend non seulement les données enregistrées plus petites, mais vous permet d’implémenter Serialize des routines qui peuvent gérer n’importe quel format de fichier.

  • La MFC est paramétrée afin que les WriteObject implémentations et ReadObject les collections associées ne soient pas liées à votre application, sauf si vous avez besoin du schéma d’archive d’objets plus général à d’autres fins.

  • Votre code n’a pas besoin de récupérer à partir d’anciens numéros de schéma. Cela rend votre code de sérialisation de document responsable de l’encodage des numéros de schéma, des numéros de version de format de fichier ou des numéros d’identification que vous utilisez au début de vos fichiers de données.

  • Tout objet sérialisé avec un appel direct à Serialize ne pas utiliser CArchive::GetObjectSchema ou doit gérer une valeur de retour de (UINT)-1 indiquant que la version était inconnue.

Étant donné qu’il Serialize est appelé directement sur votre document, il n’est généralement pas possible pour les sous-objets du document d’archiver des références à leur document parent. Ces objets doivent recevoir un pointeur vers leur document conteneur explicitement ou vous devez utiliser la fonction CArchive ::MapObject pour mapper le CDocument pointeur à un PID avant que ces pointeurs précédents ne soient archivés.

Comme indiqué précédemment, vous devez encoder vous-même les informations de version et de classe lorsque vous appelez Serialize directement, ce qui vous permet de modifier le format ultérieurement tout en conservant la compatibilité descendante avec les fichiers plus anciens. La CArchive::SerializeClass fonction peut être appelée explicitement avant de sérialiser directement un objet ou avant d’appeler une classe de base.

Voir aussi

Notes techniques par numéro
Notes techniques par catégorie