Serialización de VC
La serialización es un mecanismo proporcionado por Microsoft para la E/S de archivos en objetos. Este mecanismo se ha utilizado ampliamente en el modo Marco/Documento/Ver. Mucha gente no tiene claro qué es la serialización, cómo hacer que los objetos sean serializables y cómo utilizar la función de serialización. Este artículo intenta dar una explicación sencilla de la serialización. Como no uso mucho la función de serialización, pido disculpas por cualquier defecto.
Lectura y escritura de archivos en el marco/documento/estructura de vista MFC
CFile es la clase base para todas las clases de archivos en la biblioteca de clases MFC. Todas las funciones de E/S de archivos proporcionadas por MFC están relacionadas con esta clase. En muchos casos, a todos les gusta llamar directamente a CFile::Write/WriteHuge para escribir archivos y llamar a CFile::Read/ReadHuge para leer archivos. Este tipo de E/S de archivos en realidad no es diferente de la E/S de archivos que no usa MFC, y ni siquiera es muy diferente de la E/S de archivos anterior de ANSI C. La única diferencia es que la API llamada es diferente. .
Cuando empieces a aprender C++, debes estar muy familiarizado con cin/cout. Estos dos objetos utilizan operadores << y >> muy claros para E/S. Su formato de uso es:
<. p> p>//Código de muestra 1
int i;
cin >> i;
//aquí haz algo para objetar i
cout << i;
La ventaja de usar este método para E/S es que al usar la función de sobrecarga del operador, puede usar una declaración para completar la lectura y escritura de una serie de objetos sin tener que distinguir el tipo específico de objeto. MFC proporciona la clase CArchive, que implementa la sobrecarga de los operadores << y >>, con la esperanza de realizar E/S de archivos de la misma manera que cin y cout. Al cooperar con la clase CFile, no solo realiza la lectura y escritura de archivos de tipos simples como int/float, sino que también realiza la lectura y escritura de archivos de objetos serializables (Objetos serializables, este concepto se describirá más adelante).
Generalmente, el proceso de usar CArchive para leer objetos es el siguiente:
//Código de muestra 2
//Definir objetos de archivo y objetos de excepción de archivo
Archivo CFile;
CFileException fe;
//Abre el archivo para leer
if(!file.Open(nombre de archivo, CFile ::modeRead,&fe))
{
fe.ReportError();
return;
}
//Construir objeto CArchive
CArchive ar(&file,CArchive::load);
ar >> obj1>>obj2>>obj3...>>objn; /p>
ar.Flush();
//Después de leer, cierre el flujo de archivos
ar.Close();
archivo. Close();
El proceso de usar CArchive para escribir objetos es el siguiente:
//Código de muestra 3
//Definir objetos de archivo y excepción de archivo objetos
CFile file;
CFileException fe;
//Abre el archivo para leer
if(!file.Open(filename , CFile::modeWrite|CFile::modeCreate,&fe))
{
fe.ReportError();
return;
}
//Construir objeto CArchive
CArchive ar(&file,CArchive::load);
ar << obj1< ar.Flush(); //Después de escribir, cierre la secuencia del archivo ar.Close(); p> file.Close(); Se puede ver que para un archivo, si el orden de los objetos en el archivo es fijo, entonces los únicos operadores utilizados para la lectura y escritura del archivo son formalmente diferente. En la estructura marco/documento/vista de MFC, la composición de los objetos internos de un documento a menudo es fija. En este caso, el diseño de los objetos en el archivo cuando se escriben en el archivo también es fijo. Por lo tanto, CDocument utiliza la función virtual Serilize proporcionada por su clase base CObject para realizar la lectura y escritura automática de documentos. Cuando el usuario selecciona el menú de archivos/abrir archivo (ID_FILE_OPEN) en la interfaz, se llama automáticamente a la función OnFileOpen de la clase derivada de CWinApp. Crea (MDI)/reutiliza (SDI) marcos, documentos y. ver el objeto y finalmente llamar a CDocument::OnOpenDocument para leer el archivo. El flujo de procesamiento de CDocument::OnOpenDocument es el siguiente: //Código de muestra 4 BOOL CDocument:: OnOpenDocument(LPCTSTR lpszPathName ) { if (IsModified()) TRACE0("Advertencia: OnOpenDocument reemplaza un documento no guardado.\n"); CFileException fe; CFile* pFile = GetFile(lpszPathName, CFile::modeRead|CFile::shareDenyWrite, &fe); if (pFile == NULL) { ReportSaveLoadException(lpszPathName, &fe, FALSE, AFX_IDP_FAILED_TO_OPEN_DOC); devuelve FALSE ; } DeleteContents(); SetModifiedFlag(); // sucio durante la deseriaización CArchive loadArchive(pFile, CArchive::cargar | CArchive::bNoFlushOnDelete); loadArchive.m_pDocument = esto; loadArchive.m_bForceFlat = FALSE; TRY { CWaitCursor espera; if (pFile->GetLength() != 0) Serialize(loadArchive); // cargarme loadArchive.Close(); ReleaseFile(pFile, FALSE); } CATCH_ALL(e) { p> ReleaseFile(pFile, TRUE); DeleteContents() // eliminar contenidos fallidos TRY
> ReportSaveLoadException(lpszPathName, e,
FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
}
END_TRY
DELETE_EXCEPTION(e);
devuelve FALSE;
}
END_CATCH_ALL
SetModifiedFlag(FALSE); // comienza con sin modificar
devuelve TRUE ;
}
De manera similar, cuando el usuario selecciona el menú Archivo/Guardar archivo (ID_FILE_SAVE) o Archivo/Guardar como... (ID_FILE_SAVEAS), a través de CWinApp::OnFileSave y CWinApp : :OnFileSaveAs finalmente llama a CDocument::OnSaveDocument. Esta función se procesa de la siguiente manera:
//Código de muestra 5
BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName)
<. p> {CFileException fe;
CFile* pFile = NULL;
pFile = GetFile(lpszPathName, CFile::modeCreate |
CFile: :modeReadWrite | CFile::shareExclusive, &fe);
if (pFile == NULL)
{
ReportSaveLoadException(lpszPathName, &fe,
VERDADERO, AFX_IDP_INVALID_FILENAME);
devuelve FALSO;
}
CArchive saveArchive(pFile, CArchive::store | CArchive::bNoFlushOnDelete );
saveArchive.m_pDocument = esto;
saveArchive.m_bForceFlat = FALSE;
TRY
{
CWaitCursor espera ;
Serialize(saveArchive); // sálvame
saveArchive.Close();
ReleaseFile(pFile, FALSE);
}
CATCH_ALL(e)
{
ReleaseFile(pFile, TRUE);
TRY
{
ReportSaveLoadException(lpszPathName, e,
TRUE, AFX_IDP_FAILED_TO_SAVE_DOC) ;
}
END_TRY
DELETE_EXCEPTION(e);
devuelve FALSO;
}
END_CATCH_ALL
SetModifiedFlag(FALSE); // volver a sin modificar
return TRUE; // éxito
}
Como se puede ver en los dos fragmentos de código anteriores, las estructuras de lectura y escritura de archivos son básicamente las mismas y, en última instancia, se llama a la función CObject::Serialize para completar la lectura y escritura del documento en sí (consulte sálvame y cargame en los comentarios). Para MDI y SDI generados automáticamente por AppWizard, el sistema genera automáticamente una implementación sobrecargada de esta función. La implementación predeterminada es:
//Código de muestra 6
void CMyDoc:: Serialize(. CArchive& ar)
{
if (ar.IsStoring())
{
// TODO: agregue el código de almacenamiento aquí
}
else
{
// TODO: agregue el código de carga aquí
} p>
}
Si a una persona que está muy familiarizada con VC le gusta generar todo el código manualmente (por supuesto, esto es una pérdida de tiempo e innecesario), entonces la clase derivada de CDocument que proporciona también debería implementar. esta función Serialize predeterminada. De lo contrario, el sistema solo puede llamar a CObject::Serialize cuando lee y escribe archivos. Esta función no hace nada y, por supuesto, no puede completar el guardado/carga de archivos de objetos específicos. Por supuesto, los usuarios también pueden interceptar menús como ID_FILE_OPEN para implementar sus propias funciones de lectura y escritura de archivos, pero dicho código se volverá muy engorroso y no fácil de leer.
Volver a la función CMyDoc::Serialize. Esta función determina si el archivo se está leyendo o escribiendo actualmente juzgando el objeto ar. Dado que AppWizard no sabe para qué sirve su documento, no agregará el código de lectura y escritura del archivo real.
Supongamos que hay tres objetos m_Obj_a, m_Obj_b, m_Obj_c en su documento, entonces el código real debería ser:
//Código de muestra 7
void CMyDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << m_Obj_a << m_Obj_b << m_Obj_c; p>
}
más
{
ar >> m_Obj_a >> m_Obj_b >> m_Obj_c;
} p>
}
Objeto serializable
Una condición básica para usar el método en el código de muestra 7 para realizar E/S de archivos es: m_Obj_a, etc. El objeto debe ser un objeto serializable. Las condiciones para un objeto serializable son:
Esta clase se deriva de CObject)
Esta clase implementa la función Serializar
Esta clase se define usando la macro DECLARE_SERIAL
La macro IMPLEMENT_SERIAL se utiliza en el archivo de implementación de la clase
Esta clase tiene un constructor sin parámetros o un constructor con parámetros. Todos los parámetros se proporcionan Parámetros predeterminados
<. p>Aquí, las condiciones del objeto serializable no incluyen tipos simples. Para tipos simples, CArchive básicamente implementa la sobrecarga de los operadores << y >>, por lo que puede usar cadenas directamente en formato de fila.Derivado de la clase CObject
La serialización requiere que el objeto se derive de CObject o de una clase derivada de CObject. Este requisito es relativamente simple, porque casi todas las clases (excepto CString) se derivan de CObject, por lo que las clases que heredan de las clases MFC cumplen este requisito. Para su propia clase de datos, puede especificar su clase base como CObject para cumplir con este requisito.
Implementar la función Serializar
La función Serializar es la función que el objeto realmente guarda datos y es el núcleo de toda la serialización. El método de implementación es el mismo que CMyDoc::Serialize, usando CArchive::IsStoring y CArchive::IsLoading para determinar la operación actual y seleccionar << y >> para guardar y leer objetos.
Utilice la macro DECLARE_SERIAL
La macro DECLARE_SERIAL incluye las funciones DECLARE_DYNAMIC y DECLARE_DYNCREATE. Define la información relacionada con CRuntimeClass de una clase e implementa el operador predeterminado >> sobrecarga. Después de implementar esta macro, CArchive puede usar ReadObject y WriteObject para realizar E/S de objetos y puede leer objetos de archivos sin conocer el tipo de antemano.
Utilice IMPLEMENT_SERIAL
La macro DECLARE_SERIAL y la macro IMPLEMENT_SERIAL deben aparecer en pares; de lo contrario, las entidades definidas por la macro DECLARE_SERIAL no se implementarán, lo que eventualmente provocará errores de conexión.
Constructor predeterminado
Este es un requisito de CRuntimeClass::CreateObject para objetos.
Casos especiales
Cuando el objeto se lee y escribe solo a través de la función Serialize sin usar ReadObject/WriteObject y la sobrecarga del operador, las condiciones de serialización anteriores no son requeridas, siempre que Serialize La función está implementada. Eso es todo.
Para las clases existentes, si no proporciona funcionalidad de serialización, se puede lograr usando el operador amigos sobrecargados << y el operador >>.
Ejemplo
Supongamos que necesita implementar un programa de edición y visualización de gráficos geométricos para admitir funciones de gráficos escalables. No quiero discutir aquí la implementación del sistema de gráficos específico, solo el guardado y carga de objetos de imagen.
Clase base CPicture
Cada objeto gráfico se deriva de CPicture. Esta clase implementa la función de serialización. Su código de implementación es:
//Imagen del archivo de encabezado. h
#if !definido(__PICTURE_H__)
#define __PICTURE_H__
#if _MSC_VER > 1000
#pragma una vez
#endif // _MSC_VER > 1000
const int TYPE_UNKNOWN = -1;
clase CPicture:public CObject
{
p>
int m_nType;//Categoría gráfica
DECLARE_SERIAL(CPicture)
public:
CPicture(int m_nType=TYPE_UNKNOWN):m_nType (m_nType ){};
int GetType()const {return m_nType;};
virtual void Draw(CDC * pDC);
void Serialize( CArchive & ar);
};
#endif
//cpp archivo imagen.cpp
#include "stdafx.h "
#include "imagen.h"
#ifdef _DEBUG
#definir nuevo DEBUG_NEW
#undef ESTE_FILE
static char THIS_FILE[] = __FILE__;
#endif
void CPicture::Draw(CDC * pDC)
{
//La clase base no implementa la función de dibujo, la implementa la clase derivada
}
void CPicture::Serialize(CArchive & ar)
{
if(ar.IsLoading())
{
ar << m_nType;
}else{
ar >> m_nType;
}
}
Nota: Dado que CRuntimeClass requiere que se cree una instancia de este objeto, aunque la función Draw No tiene ninguna operación de dibujo, esta clase todavía no está definida como una función virtual pura.
Proceso de guardado y E/S de archivos de objetos en clases derivadas de CDocument
Para simplificar el diseño, la clase de plantilla CPtrList proporcionada por MFC se utiliza para guardar objetos en CDocument- clases derivadas.
El objeto se define como:
protected:
CTypedPtrList m_listPictures;
Dado que ni CTypedPtrList ni CPtrList implementan la función Serialize, no puede pasar ar << m_listPictures y ar >> m_listPictures para serializar el objeto, por lo que la función Serialize de CPictureDoc debe implementarse de la siguiente manera:
void CTsDoc::Serialize(CArchive& ar)
{
POSITION pos;
if (ar.IsStoring())
{
// TODO: agregue el código de almacenamiento aquí
pos = m_listPictures .GetHeadPosition();
while(pos != NULL)
{
ar << m_listPictures.GetNext (pos); p>
}
}
else
{
// TODO: agregue el código de carga aquí
Eliminar todo( );
CPicture * pPicture;
hacer{
intentar
{
ar >> pPicture;
TRACE("Leer objeto %d\n",pPicture->GetType ());
m_listPictures.AddTail(pPicture);
}
p>
catch(CException * e)
{
e->Delete ();
break ;
}
} while(pImagen != NULL);
}
m_pCurrent = NULL;
SetModifiedFlag(FALSE);
}
Implementar la clase derivada
Función de serialización
El programa de gráficos geométricos admite líneas rectas, rectángulos, triángulos, elipses y otros gráficos, que se implementan con las clases CLine, CRectangle, CTriangle y CEllipse respectivamente. Tome la clase CLine como ejemplo para implementar la función de serialización:
Derive CLine de CPicture y agregue las siguientes variables miembro a la definición de clase CLine: CPoint m_ptStart,m_ptEnd;
Bajo esto línea Agregue la siguiente macro a una línea: DECLARE_SERIAL(CLine)
Implemente la función Serializar void CLine::Serialize(CArchive & ar)
{
CPicture ::Serialize(ar );
if(ar.IsLoading())
{
ar>>m_ptStart.x>>m_ptStart.y>> m_ptEnd.x>> m_ptEnd.y;
}else{
ar< } } Agregar IMPLEMENT_SERIAL(CLine,CPicture,TYPE_LINE); La CLine definida de esta manera tiene la función de serialización y Otras clases de gráficos se pueden definir de manera similar.