c++ - Boost:: Serialización y arquitectura de Doc/View de MFC
boost-serialization (2)
Estoy portando una aplicación MFC C ++ existente para usar Boost :: Serialización para archivos XML. Mi objeto CDocument contiene todos los datos de la aplicación. Implementé la función serializar como:
template<class Archive>
void CMyDoc::serialize(Archive& ar, const unsigned int version)
{
ar & BOOST_SERIALIZATION_NVP(m_Param1)
& BOOST_SERIALIZATION_NVP(m_Param2);
}
Para capturar los eventos guardar y cargar, en el archivo CDoc * .cpp he sobrecargado las funciones de la clase base OnOpenDocument () y OnSaveDocument () para implementar Boost :: Serialization:
BOOL CMyDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
clear(); // clear current params
//if (!CDocument::OnOpenDocument(lpszPathName)) // Old MFC serialize code
// return FALSE;
CEvolveTrafficDoc* pDoc = this; // pointers the same here
std::ifstream ifs(lpszPathName);
boost::archive::xml_iarchive ia(ifs);
ia >> boost::serialization::make_nvp("MyDoc",pDoc); // pointer changes here
// *this = *pDoc; // POSSIBLE solution with CMyDoc copy constructor implemented
return TRUE;
}
BOOL CMyDoc::OnSaveDocument(LPCTSTR lpszPathName)
{
//if (!CDocument::OnSaveDocument(lpszPathName)) // Old MFC serialize code
// return FALSE;
std::ofstream ofs(lpszPathName);
boost::archive::xml_oarchive oa(ofs);
oa << boost::serialization::make_nvp("MyDoc",this);
return TRUE;
}
Guardar un documento funciona bien. El problema es que cargar un documento no funciona. La biblioteca de impulso parece copiar el objeto CMyDoc porque el puntero devuelve una dirección diferente. Esto significa que el archivo cargado no se carga en el documento actual. ¿Puede un CDoc sobrescribirse con boost? Puede con MFC CArchive.
Pensé en hacer que la línea se indicara como "solución POSIBLE", pero esto significaría implementar el constructor de copia para la clase CMyDoc. Esto elimina uno de los beneficios del impulso ya que tendré dos líneas de código para cada variable: 1. ar & BOOST_SERIALIZATION_NVP (m_Param1) // para guardar y cargar en pDoc 2. this-> m_Param1 = pDoc.m_Param1 // en CMyDoc copy constructor
Si sobrecargo el CMyView para capturar el archivo abierto y guardar eventos, la administración de la lista MRU que ofrece la arquitectura Doc / View no se realizará.
Estoy seguro de que esto se ha hecho un millón de veces, pero no puedo encontrar ninguna información en línea. ¡Extraño! Cualquier ayuda muy apreciada: D
Leyendo la documentación más de cerca, veo que Boost reconoce que cualquier puntero serializado se deserializa con una nueva palabra clave: "La serialización de los punteros se implementa en la biblioteca con un código similar al siguiente:"
// load data required for construction and invoke constructor in place
template<class Archive, class T>
inline void load_construct_data(
Archive & ar, T * t, const unsigned int file_version
){
// default just uses the default constructor to initialize
// previously allocated memory.
::new(t)T();
}
La documentación recomienda sobrecargar esta función si es necesario:
template<class Archive>
inline void load_construct_data(
Archive & ar, my_class * t, const unsigned int file_version
){
// retrieve data from archive required to construct new instance
int attribute;
ar >> attribute;
// invoke inplace constructor to initialize instance of my_class
::new(t)my_class(attribute);
}
Pero esto nuevamente resultaría en la necesidad de implementar un constructor de copia CMyDoc. Aaarrgghhhh !!
En caso de que ayude a alguien, tuve una respuesta de Robert Ramey sobre esto. Básicamente, no me faltaba algo obvio: la función CMyDoc serialize (Archive & ar, const unsigned int version) no era un corredor, así que implementé las funciones separadas boost_save y boost_load. Tuve que sobrecargar OnOpenDocument y OnSaveDocument, por ejemplo:
BOOL CMyDoc :: OnOpenDocument (LPCTSTR lpszPathName) {clear ();
// Call base class function with empty local Serialize function
// to check file exists etc
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
std::string file( lpszPathName );
boost_load(file);
return TRUE;
}
Esto es necesario ya que MFC CArchive posee el archivo hasta que la función de serialización de MFC se cierra, no permitiendo boost :: serialization para acceder al archivo. Incluso la invocación de ar.Abort () en las funciones de serialización no funciona porque la clase base de CDocument asume que ar existe al regresar a la función Serialize de la clase base.
Al volver a leer lo anterior, veo que no es una respuesta completa. Espero que esto sea más útil.
En primer lugar (¡y lo más importante, como lamentablemente descubrí!), CMyDoc
su primer parámetro en una variable de número de versión CMyDoc
. Tengo un miembro protegido: unsigned int m_Version;
y luego el constructor de la clase es:
CMyDoc::CMyDoc(): m_Version(1) // The current file structure version
{
// construction code
}
Esto le permite leer versiones de archivos anteriores fácilmente. Aquí están las cuatro funciones, dos para cargar y guardar.
Para cargar:
BOOL CMyDoc::OnOpenDocument(LPCTSTR lpszPathName)
{
clear(); // avoid memory leaks if necessary
// Call base class function with empty local Serialize function
// to check file etc
if (!CDocument::OnOpenDocument(lpszPathName))
return FALSE;
std::string file( lpszPathName );
try {
boost_load(file);
}
catch (const boost::archive::archive_exception& ae)
{
CFrameWnd * pFrame = (CFrameWnd *)(AfxGetApp()->m_pMainWnd);
CView * pView = pFrame->GetActiveView();
ostringstream str;
str << "Problem loading file./n"
<< "Boost reports: " << ae.what() << ''/n''
<< "Possibly: " << extraArchiveWhat(ae.what())
<< std::ends;
MessageBox(pView->GetSafeHwnd(),str.str().c_str(), "MyApp", MB_OK|MB_ICONERROR);
return FALSE;
}
// If we get here we have been successful
return TRUE;
}
int CSimbaDoc::boost_load(std::string file)
{
std::ifstream ifs(file);
boost::archive::xml_iarchive ia(ifs);
int file_version; // local so as not to write over class m_Version
ia >> boost::serialization::make_nvp("m_Version", file_version)
>> BOOST_SERIALIZATION_NVP(m_Param1)
>> BOOST_SERIALIZATION_NVP(m_Param2);
if(file_version > 0) // read a variable added to class after version 0
ia >> BOOST_SERIALIZATION_NVP(m_Param3);
// and anything else you need to read
}
Y para guardar:
BOOL CMyDoc::OnSaveDocument(LPCTSTR lpszPathName)
{
// Call base class function with empty local Serialize function
// to check file etc
if (!CDocument::OnSaveDocument(lpszPathName))
return FALSE;
std::string file( lpszPathName );
boost_save(file);
return TRUE;
}
int CSimbaDoc::boost_save(std::string file)
{
std::ofstream ofs(file);
boost::archive::xml_oarchive oa(ofs);
oa << BOOST_SERIALIZATION_NVP(m_Version) // always save current class version
<< BOOST_SERIALIZATION_NVP(m_Param1)
<< BOOST_SERIALIZATION_NVP(m_Param2)
<< BOOST_SERIALIZATION_NVP(m_Param3);
// and whatever else
}
Hay una solución muy buena usando Boost.IOStreams:
// We mean to not track this class, or you''ll get object-tracking warnings
BOOST_CLASS_TRACKING(MyDoc, boost::serialization::track_never)
void MyDoc::Serialize(CArchive& ar)
{
namespace io = boost::iostreams;
io::file_descriptor fd(ar.GetFile()->m_hFile, io::never_close_handle);
io::stream<io::file_descriptor> file(fd);
if (ar.IsStoring())
{
boost::archive::xml_oarchive oa(file);
oa << *this;
}
else
{
boost::archive::xml_iarchive ia(file);
ia >> *this;
// then update the views...
}
}
template<class Archive>
void MyDoc::serialize(Archive & ar, unsigned version)
{
// Your Boost.Serialization code here
ar & BOOST_SERIALIZATION_NVP(member);
}
No tiene que preocuparse por OnOpenDocument / OnSaveDocument. Simplemente sobrescriba el CDocument :: Serialize y reenvíelo a Boost.Serialization.