Cette note décrit les routines MFC qui prennent en charge les objets C++ persistantes et le format des données de l'objet lorsqu'il est stocké dans un fichier. Cela s'applique uniquement aux classes avec les macros DECLARE_SERIAL et IMPLEMENT_SERIAL.
Le problème
La mise en œuvre de MFC pour les données persistantes s'appuie sur un format binaire compact pour enregistrer les données de nombreux objets en une seule partie contiguë d'un fichier. Ce format binaire fournit la structure de la façon dont les données sont stockées, mais il est fonction de l'objet Serialize membre qui fournit les données réelles enregistrées par l'objet.
Le MFC résout le problème de structuration en utilisant la classe CArchive. Un objet CArchive fournit un contexte de persistance qui dure depuis l'archive est créé jusqu'à ce que la fonction membre CArchive::Close est appelée, soit explicitement par le programmeur, soit implicitement par le destructeur quand il est sortie de la portée contenant l' CArchive.
Cette note décrit l'implémentation des membres CArchive ReadObject et WriteObject. ReadObject et WriteObject ne sont pas appelés directement par sont plutôt utilisées par les classe spécifique type-safe d'insertion et d'extraction opérateurs générée automatiquement par les macros DECLARE_SERIAL et IMPLEMENT_SERIAL.
classe CMyObject : public CObject
{
nbsp ; DECLARE_SERIAL(CMyObject)
};
IMPLEMENT_SERIAL (CMyObj, CObject, 1)
/ / exemple d'utilisation (ar est un CArchive &)
CMyObject * pObj ;
CArchive & ar ;
AR << pObj ; / / appels ar.WriteObject(pObj)
AR >> pObj ; / / appels ar.ReadObject(RUNTIME_CLASS(CObj))
Cette note décrit le code situé dans le fichier source MFC ARCOBJ.RPC. La mise en œuvre CArchive principal se trouvent dans ARCCORE.RPC.
Enregistrement des objets dans le magasin (CArchive::WriteObject)
La fonction membre CArchive::WriteObject écrit les données d'en-tête utilisées pour reconstruire l'objet. Ces données se compose de deux parties : le type de l'objet et l'état de l'objet. Cette fonction membre est également responsable du maintien de l'identité de l'objet en cours d'écriture, ainsi que qu'une seule copie est sauvée, quel que soit le nombre de pointeurs de cet objet (y compris les pointeurs circulaires).
Sauver (insertion) et de restaurer des objets (extraction) s'appuie sur plusieurs « constantes manifestes. » Ce sont des valeurs qui sont stockées en binaire et de fournissent des renseignements importants à l'archive (Notez le préfixe « w » indique les quantités de 16-bit):
| Balise | Description |
| wNullTag | Utilisé pour des pointeurs d'objet NULL (0). |
| wNewClassTag | Indique la description de classe qui suit est nouveau pour cette context—(-1) archive. |
| wOldClassTag | Indique la classe de l'objet est lu a été vu dans ce contexte (0 x 8000). |
Lorsque le stockage d'objets, l'archive conserve un CMapPtrToPtr ( m_pStoreMap) qui est un mappage entre un objet stocké un identificateur persistant de 32 bits (PID). Un PID est affecté à chaque objet unique et chaque nom de classe unique qui est enregistré dans le contexte de l'archive. Ces PIDs sont remis séquentiellement à partir de 1. Il est important de noter que ces PIDs n'ont aucune signification en dehors de la portée de l'archive et, en particulier, sont à ne pas confondre avec le numéro d'enregistrement ou d'autres pièces d'identité.
À partir de la classe CArchive MFC version 4.0 a été étendu à l'appui des archives très importantes. Dans les versions précédentes, un PID est une quantité de 16 bits, limitant les Archives 0x7FFE (32766) aux objets. PIDs sont maintenant 32 bits, mais ils sont écrits 16-bit à moins qu'ils sont plus gros que 0x7FFE. Grands PIDs sont écrites en 0x7FFF suivi par le PID de 32 bits. Cette technique maintient la compatibilité descendante de fichier.
Lorsqu'une demande est faite pour enregistrer un objet dans une archive (habituellement par le biais de l'opérateur global d'insertion), un contrôle est effectué pour un pointeur NULL CObject ; Si le pointeur est NULL, le wNullTag est insérée dans le flux de l'archive.
Si nous avons un pointeur d'objet réel qui est capable d'être sérialisés (la classe est une classe DECLARE_SERIAL ), nous vérifions ensuite la m_pStoreMap pour voir si l'objet a été enregistré déjà. Si elle a, nous insérons le PID de 32 bits associé à cet objet.
Si l'objet n'a pas été enregistré avant, il y a deux possibilités, nous devons prendre en compte : l'objet et le type exact (c'est-à-dire, classe) de l'objet sont de nouveau dans ce contexte d'archive ou l'objet est d'un type exact de déjà vu. Pour déterminer si le type a vu que nous interroger la m_pStoreMap pour un objet CRuntimeClass qui correspond à l'objet CRuntimeClass associé à l'objet que nous économisons. Si nous l'avons vu cette classe avant, WriteObject insère une balise qui est le bit OR'ing de wOldClassTag et de cet indice. Si le CRuntimeClass est nouveau dans ce contexte d'archive, puis WriteObject assigne un nouveau PID à cette classe et les insérer dans l'archive, précédée par la valeur de wNewClassTag.
Le descripteur de cette classe est ensuite inséré dans l'archive en utilisant la fonction membre CRuntimeClass magasin. 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 à toutes les applications, donc il est conseillé de baliser vos fichiers de données pour prévenir la corruption. Après l'insertion d'informations des classe, l'archive place l'objet dans le m_pStoreMap et appelle ensuite la fonction de membre Serialize pour insérer des données spécifiques à la classe dans l'archive. Placer l'objet dans l' m_pStoreMap avant d'appeler Serialize empêche plusieurs copies de l'objet d'être enregistrés dans le magasin.
Lors du retour à l'appelant initial (habituellement la racine du réseau des objets), il est important fermer l'archive. Si d'autres opérations de CFile sont à faire, la fonction de membre CArchive Flush doit être appelée. Défaut de le faire entraînera une archive corrompue.
&Notenbsp ; Cette mise en œuvre impose une limite dure des indices de 0x3FFFFFFE par le contexte de l'archive. Ce nombre représente le nombre maximal d'objets uniques et des classes qui peuvent être enregistrés dans une seule archive, mais note qu'un seul disque fichier peut avoir un nombre illimité de contextes d'archive.
Chargement des objets du magasin (CArchive::ReadObject)
Chargement des objets (extraction) utilise la fonction de membre de CArchive::ReadObject et est l'inverse de WriteObject. Comme avec WriteObject, ReadObject n'est pas appelée directement par le code de l'utilisateur ; code utilisateur doit appeler l'opérateur d'extraction de type sécurisé qui appelle ReadObject avec les attendus CRuntimeClass. Cela assure l'intégrité du type de l'opération de l'extrait.
Depuis la mise en œuvre WriteObject assigné PIDs croissantes, à partir de 1 (0 est prédéfini comme l'objet NULL), la mise en œuvre de ReadObject peut utiliser un tableau pour maintenir l'État du contexte de l'archive. Quand un PID est lu à partir du magasin, si le PID est supérieur à la limite supérieure actuelle de la m_pLoadArray, puis ReadObject sait qu'un nouvel objet (ou une description de classe) suit.
Numéros de schéma
Le numéro de schéma, qui est assigné à la classe quand IMPLEMENT_SERIAL la classe est rencontrée, est la « version » de l'implémentation de la classe. Le schéma fait référence à l'implémentation de la classe, pas pour le nombre de fois où un objet donné a persistant (généralement appelé la version de l'objet).
Si vous avez l'i&ntention de maintenir plusieurs implémentations différentes d'une même classe au fil du temps, en incrémentant le schéma que vous modifier l'implémentation de fonction membre votre objet Serialize vous permettra d'écrire du code qui peut charger des objets stockés à l'aide de versions antérieures de la implementation.nbsp;
La fonction de membre CArchive::ReadObject lèvera un CArchiveException lorsqu'il rencontre un certain nombre de schéma dans le magasin persistant qui diffère de la description de classe en mémoire le numéro de schéma. Il n'est pas facile de se remettre de cette exception.
Vous pouvez utiliser VERSIONABLE_SCHEMA ou avait avec votre version de schéma de garder cette exception d'être levée. À l'aide de VERSIONABLE_SCHEMA, votre code peut prendre les mesures appropriées à sa fonction Serialize en vérifiant la valeur de retour de CArchive::GetObjectSchema.
Appel sérialiser directement
Il existe de nombreux cas où les frais généraux du régime général objet archive WriteObject et ReadObject n'est pas nécessaire ou souhaité. C'est le cas commun de sérialiser les données dans un CDocument. Dans ce cas, la fonction de membre de Serialize de la CDocument n'est appelée directement, pas avec l'extrait ou insérer des opérateurs. Le contenu du document peut à son tour utiliser le régime d'archive objet plus général.
Appeler Serialize directement a les avantages et les inconvénients suivants:
Parce que Serialize est appelée directement sur votre document, il n'est pas généralement possible pour les sub-objects du document à archiver des références à leur document parent. Ces objets doivent recevoir un pointeur à leur document conteneur explicitement ou vous devez utiliser la fonction CArchive::MapObject pour mapper le pointeur CDocument un PID avant ces pointeurs arrière sont archivés.
Comme indiqué plus haut, vous devez coder les informations de version et de la classe vous-même lors de l'appel Serialize directement, vous permettant de modifier le format plus tard tout en conservant la compatibilité ascendante avec les anciens fichiers. La fonction CArchive::SerializeClassRef peut être appelée explicitement avant la sérialisation d'un objet directement, ou avant d'appeler une classe de base.
&Notes techniques par le numéro |nbsp ; Notes techniques par catégorie