开发者

Boost::Serialization and MFC Doc/View architecture

I'm porting an existing MFC C++ application to use Boost::Serialization for XML files. My CDocument object contains all the data for the app. I've implemented the serialize function as:

template<class Archive>
void CMyDoc::serialize(Archive& ar, const unsigned int version)
{
ar  & BOOST_SERIALIZATION_NVP(m_Param1)
    & BOOST_SERIALIZATION_NVP(m_Param2);
}

To capture the save and load events, in the CDoc *.cpp file I have overloaded the base class functions OnOpenDocument() and OnSaveDocument() to implement 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;
}

Saving a document works fine. The problem is that loading a document doesn't work. The boost library seems to copy the CMyDoc object because the pointer comes back a different address. This means that the loaded file isn't loaded into the current document. Can a开发者_如何转开发 CDoc overwrite itself with boost? It can with MFC CArchive.

I thought about having the line indicated as "POSSIBLE solution", but this would mean implementing the copy constructor for the CMyDoc class. This removes one of the benefits of boost in that I would have two lines of code for each variable: 1. ar & BOOST_SERIALIZATION_NVP(m_Param1) // for saving and loading to pDoc 2. this->m_Param1 = pDoc.m_Param1 // in CMyDoc copy constructor

If I overload the CMyView to capture the file open and save events, the MRU list management offered by the Doc/View architecture won't happen.

I'm sure this has been done a million times, but I can't find any information online. Weird! Any help much appreciated :D


Reading the documentation closer, I see that Boost acknowledges that any serialized pointer is deserialized with a new keyword: "Serialization of pointers is implemented in the library with code similar to the following:"

// 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();
}

The documentation recommends overloading this function if necessary:

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

But this would again result in needed to implement a CMyDoc copy constructor. Aaarrgghhhh!!


In case it helps anyone, I had a reply from Robert Ramey about this. Basically, I wasn't missing something obvious: the CMyDoc serialize(Archive& ar, const unsigned int version) function wasn't a runner, so I implemented separate boost_save and boost_load functions. I had to overload the OnOpenDocument and OnSaveDocument, for example:

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;

}

This is necessary since the MFC CArchive owns the file until the MFC Serialize function exits, disallowing boost::serialization to access the file. Even calling ar.Abort() in the Serialize functions doesn't work because the CDocument base class assumes the ar exists on returning to the base class Serialize function.


There's a very neat solution using 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);
}

You don't have to bother with OnOpenDocument/OnSaveDocument. Just overwrite the CDocument::Serialize and forward it to Boost.Serialization.


Re-reading the above I see it's not a complete answer. Hopefully this is more helpful.

Firstly (and importantly as I regrettably found out!), make your first parameter a CMyDoc version number variable. I have a protected member: unsigned int m_Version; and then the class constructor is:

CMyDoc::CMyDoc(): m_Version(1)  // The current file structure version
{
    // construction code
} 

This allows you to read previous file versions easily. Here are the four functions, two each for loading and saving.

For loading:

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
}

And for saving:

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
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜