Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En esta nota se describen las rutinas de MFC que admiten objetos C++ persistentes y el formato de los datos del objeto cuando se almacenan en un archivo. Esto solo se aplica a las clases con las macros DECLARE_SERIAL y IMPLEMENT_SERIAL .
El problema
La implementación de MFC para el almacenamiento de datos persistentes guarda los datos de muchos objetos en una única parte contigua de un archivo. El método del Serialize
objeto convierte los datos del objeto en un formato binario compacto.
La implementación garantiza que todos los datos se guardan en el mismo formato mediante la clase CArchive. Usa un CArchive
objeto como traductor. Este objeto persiste desde el momento en que se crea hasta que se llama a CArchive::Close. El programador puede llamar a este método explícitamente o el destructor lo puede llamar implícitamente cuando el programa sale del ámbito que contiene el CArchive
.
En esta nota se describe la implementación de los CArchive
miembros CArchive::ReadObject y CArchive::WriteObject. Encontrará el código de estas funciones en Arcobj.cpp y la implementación principal de CArchive
en Arccore.cpp. El código de usuario no llama a ReadObject
ni WriteObject
directamente. En su lugar, estos objetos se usan mediante operadores de inserción y extracción seguros para tipos específicos de clase generados automáticamente por las macros DECLARE_SERIAL y IMPLEMENT_SERIAL. El código siguiente muestra cómo WriteObject
y ReadObject
se llaman implícitamente:
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))
Guardar objetos en el Almacén (CArchive::WriteObject)
El método CArchive::WriteObject
escribe los datos de encabezado que se usan para reconstruir el objeto. Estos datos constan de dos partes: el tipo del objeto y el estado del objeto. Este método también es responsable de mantener la identidad del objeto que se está escribiendo, de modo que solo se guarde una sola copia, independientemente del número de punteros a ese objeto (incluidos los punteros circulares).
Guardar (insertar) y restaurar (extraer) objetos se basa en varias "constantes de manifiesto". Estos son valores almacenados en binarios y proporcionan información importante al archivo (tenga en cuenta que el prefijo "w" indica cantidades de 16 bits):
Etiqueta | Descripción |
---|---|
wNullTag | Se usa para punteros de objeto NULL (0). |
wNewClassTag | Indica que la descripción de la clase que sigue es nueva para este contexto de archivo (-1). |
wOldClassTag | Indica que la clase del objeto que se lee se ha visto en este contexto (0x8000). |
Al almacenar objetos, el archivo mantiene un CMapPtrToPtr (el m_pStoreMap) que es una asignación de un objeto almacenado a un identificador persistente de 32 bits (PID). Un PID se asigna a cada objeto único y a cada nombre de clase único que se guarda en el contexto del archivo. Estos PID se entregan secuencialmente a partir de 1. Estos PID no tienen ninguna importancia fuera del ámbito del archivo y, en particular, no deben confundirse con números de registro u otros elementos de identidad.
En la CArchive
clase , los PID son de 32 bits, pero se escriben como de 16 bits a menos que sean mayores que 0x7FFE. Los PID grandes se escriben como 0x7FFF seguidos del PID de 32 bits. Esto mantiene la compatibilidad con proyectos creados en versiones anteriores.
Cuando se realiza una solicitud para guardar un objeto en un archivo (normalmente mediante el operador de inserción global), se realiza una comprobación para un puntero CObject NULL. Si el puntero es NULL, wNullTag se inserta en el flujo de archivo.
Si el puntero no es NULL y se puede serializar (la clase es una DECLARE_SERIAL
clase), el código comprueba el m_pStoreMap para ver si el objeto ya se ha guardado. Si lo tiene, el código inserta el PID de 32 bits asociado a ese objeto en la secuencia de archivo.
Si el objeto no se ha guardado antes, hay dos posibilidades de tener en cuenta: tanto el objeto como el tipo exacto (es decir, la clase) del objeto son nuevos en este contexto de archivo o el objeto es de un tipo exacto que ya se ha visto. Para determinar si se ha visto el tipo, el código consulta el m_pStoreMap de un objeto CRuntimeClass que coincide con el CRuntimeClass
objeto asociado al objeto que se va a guardar. Si hay una coincidencia, WriteObject
inserta una etiqueta que es el resultado en forma binaria de OR
y este índice. Si el CRuntimeClass
es nuevo en este contexto de archivo, WriteObject
asigna un nuevo PID a esa clase e insertarlo en el archivo, precedido por el valor wNewClassTag.
A continuación, el descriptor de esta clase se inserta en el archivo mediante el CRuntimeClass::Store
método .
CRuntimeClass::Store
inserta el número de esquema de la clase (vea a continuación) y el nombre de texto ASCII de la clase. Tenga en cuenta que el uso del nombre de texto ASCII no garantiza la unicidad del archivo entre aplicaciones. Por lo tanto, debe etiquetar los archivos de datos para evitar daños. Después de la inserción de la información de clase, el archivo coloca el objeto en el m_pStoreMap y, a continuación, llama al Serialize
método para insertar datos específicos de la clase. Colocar el objeto en el m_pStoreMap antes de llamar Serialize
evita que se guarden varias copias del objeto en el almacén.
Al volver al llamador inicial (normalmente la raíz de la red de objetos), debe llamar a CArchive::Close. Si tiene previsto realizar otras operaciones de CFile , debe llamar al CArchive
método Flush para evitar daños en el archivo.
Nota:
Esta implementación impone un límite estricto de índices de 0x3FFFFFFE por contexto de archivo. Este número representa el número máximo de objetos y clases únicos que se pueden guardar en un único archivo, pero un único archivo de disco puede tener un número ilimitado de contextos de archivo.
Cargar objetos desde el almacén (CArchive::ReadObject)
Cargar (extraer) objetos usa el método CArchive::ReadObject
y es el inverso de WriteObject
. Al igual que con WriteObject
, el código de usuario no llama directamente a ReadObject
; en su lugar, debe llamar al operador de extracción seguro para tipos que invoca ReadObject
con el CRuntimeClass
esperado. Esto garantiza la integridad de tipo de la operación de extracción.
Dado que la WriteObject
implementación asignó PID crecientes, a partir de 1 (0 está predefinida como el objeto NULL), la ReadObject
implementación puede usar una matriz para mantener el estado del contexto de archivo. Cuando se lee un PID del almacén, si el PID es mayor que el límite superior actual de la m_pLoadArray, ReadObject
sabe que sigue un nuevo objeto (o descripción de clase).
Números de esquema
El número de esquema, que se asigna a la clase cuando se encuentra el IMPLEMENT_SERIAL
método de la clase, es la "versión" de la implementación de clase. El esquema hace referencia a la implementación de la clase , no al número de veces que se ha hecho persistente un objeto determinado (normalmente denominado versión del objeto).
Si tiene previsto mantener varias implementaciones diferentes de la misma clase a lo largo del tiempo, incrementar el esquema a medida que revise la implementación del método del Serialize
objeto le permitirá escribir código que pueda cargar objetos almacenados mediante versiones anteriores de la implementación.
El CArchive::ReadObject
método lanzará una Excepción CArchiveException cuando encuentre un número de esquema en el almacén persistente que difiera del número de esquema de la descripción de clase en memoria. No es fácil recuperarse de esta excepción.
Puede usar VERSIONABLE_SCHEMA
combinado con ( OR bit a bit) la versión del esquema para evitar que se produzca esta excepción. Mediante el uso de VERSIONABLE_SCHEMA
, su código puede realizar la acción adecuada en su función Serialize
comprobando el valor devuelto de CArchive::GetObjectSchema.
Llamar a Serializar directamente
En muchos casos, la sobrecarga del esquema general de archivo de objetos de WriteObject
y ReadObject
no es necesaria. Este es el caso común de serializar los datos en un CDocument. En este caso, se llama directamente al Serialize
método de CDocument
, no con los operadores de extracción o inserción. A su vez, el contenido del documento puede usar el esquema de archivo de objetos más general.
Llamar Serialize
directamente tiene las siguientes ventajas y desventajas:
No se agregan bytes adicionales al archivo antes o después de serializar el objeto. Esto no solo reduce los datos guardados, sino que permite implementar
Serialize
rutinas que pueden controlar cualquier formato de archivo.El MFC está optimizado para que las
WriteObject
implementaciones yReadObject
las colecciones relacionadas no se vinculen a la aplicación, a menos que necesite el esquema de archivo de objetos más general para algún otro propósito.El código no tiene que recuperarse de números de esquema antiguos. Esto hace que el código de serialización de documentos sea responsable de codificar números de esquema, números de versión de formato de archivo o cualquier número de identificación que use al principio de los archivos de datos.
Cualquier objeto que se serialice con una llamada directa a
Serialize
no debe usarCArchive::GetObjectSchema
o debe controlar un valor devuelto de (UINT)-1 que indique que la versión era desconocida.
Dado que Serialize
se invoca directamente en el documento, normalmente no es posible que los sub-objetos del documento mantengan referencias a su documento principal. Estos objetos se les debe proporcionar un puntero a su documento contenedor correspondiente explícitamente o debe usar la función CArchive::MapObject para asignar el puntero CDocument
a un PID antes de que se archiven estos punteros inversos.
Como se mencionó antes, debe codificar usted mismo la información de la versión y la clase al llamar directamente a Serialize
, permitiendo cambiar el formato más adelante mientras sigue manteniendo la compatibilidad con versiones anteriores de archivos antiguos. Se puede llamar a la CArchive::SerializeClass
función explícitamente antes de serializar directamente un objeto o antes de llamar a una clase base.