XML Serialization for MFC

The native object serialization offered by MFC (CArchive, CObject::Serialize) has several disadvantages. The major disadvantages result from the fact that it is a binary serialization. This results in:

  1. Non-robustness—your program will probably crash if you read an archive produced by another version of your program. This can be avoided by complex and unwieldly version management. By using XML, this can be largely avoided.
  2. Heavy dependencies between your program object model and the archived data. Change the program model and it is almost impossible to read data from a previous version.
  3. Archived data cannot be edited, understood, and changed, except with the associated application.
  4. Archived data cannot be exchanged across platforms.
  5. Application configuration data can be generated by other tools in XML that can be directly imported by your application.

However, it has one major advantage; it is easy to use.

I have tried to remove the disadvantages by serializing objects to XML, and retaining the ease of use. It's enough to use the various macros in your Serialize method. For example, consider the following code:

// .h file
class CSomeClass : public CObject
{
public:
  CSomeClass();
  DECLARE_XMLSERIAL(CSomeClass)

// Attributes
public:
  enum {eFIRST, eSECOND} m_enumAttribute;
  int     m_intAttribute;
  bool    m_boolAttribute;
  CString m_stringAttribute;

// Operations
  virtual void Serialize(CArchive& ar);

}

// .cpp file
IMPLEMENT_XMLSERIAL(CSomeClass, CObject)

void CSomeClass::Serialize(CArchive& ar)
{
   XMLCLASSNODE
   XMLINTDATA(m_enumAttribute);
   XMLDATA(m_intAttribute);
   XMLDATA(m_boolAttribute);
   XMLDATA(m_stringAttribute);
   ENDNODE
}

Follow these steps to use in a standard MFC application (see sample project):

  1. Include the files XMLArchive.cpp and XMLArchive.hpp into your project.
  2. Copy the supplied stdafx.h fragment into your stdafx.h.
  3. Override your CDocument derived class's OnSaveDocument to use CXMLArchive instead of the normal CArchive.
  4. Enclose your Serialize methods with the macros XMLCLASSNODE and XMLENDNODE. Within this block, use the XMLDATA to serialize your attributes. Use XMLINDATA for enums (it casts them to int). Call any base class Serialize method from within the XMLCLASSNODE, XMLENDNODE block.

Use the application to create a new document and save it to disk. Use Internet explorer or XML Notepad to examine the contents of the file. You will se the application's object model preserved in XML.

Use the debugger to verify that the file is reloaded correctly when opening the file.

This method of serialization can serialize complex object models, but objects that are referenced n times are serialized n times and not only once. So, try to avoid multiple object references.

Downloads

Download demo project - 33 Kb
Download source - 6 Kb


Comments

  • Any idea of how can I add cptrlist support?

    Posted by Mythos on 09/17/2005 06:29am

    I'm trying to add cptrlist support (pratically I need to load new classes instances and store their pointers inside the cptrlist.
    I've managed to write this:
    
    void CXMLArchiveNode::DataNode(LPCTSTR attrName, CPtrList& ptrList) {
    	if (m_nodePtr == NULL) { return; }
    
    	CXMLArchiveNode* curNodePtr = m_archivePtr->GetNode(attrName);
    	if (m_archivePtr->IsStoring()) {
    		for (POSITION Pos = ptrList.GetHeadPosition(); Pos != NULL;) {
    			((CObject*) ptrList.GetNext(Pos))->Serialize(*m_archivePtr);
    		}
    	}
    	else {
    		ptrList.RemoveAll();		
    		CObject *objectPtr;
    		int numberObjects = curNodePtr->GetNoChildren();
    		for (m_childIndex = 0; m_childIndex < numberObjects; m_childIndex++) {
    			CString childNodeName = curNodePtr->GetChildName(0);
    			objectPtr = CreateObject(childNodeName);
    			if (objectPtr == NULL) {
    				ASSERT(FALSE);
    				break;
    			}
    			int m_childIndexBefore = m_childIndex;
    			objectPtr->Serialize(*m_archivePtr);
    			m_childIndex = m_childIndexBefore;
    			ptrList.AddTail(objectPtr);
    		}
    	}
    
    	m_childIndex = 0;
    	curNodePtr->Close();
    }
    
    but strangely when I'm loading the class that get created have all the same data. Any idea?
    
    BTW is there a way to add line breaks to the xml file? It's almost impossible to understand it with a text editor.
    
    Thanks in advance.

    • Re: Any idea of how can I add cptrlist support?

      Posted by peterburn on 11/09/2005 09:12pm

      In the main loading loop, use m_archivePtr->m_childIndex rather than m_childIndex. Remove m_childIndexBefore.

      Reply
    Reply
  • Some fixes

    Posted by GuidoB on 02/28/2005 09:32am

    Thanks a lot for this article. It was an
    excellent starting point for my XML serialization
    routine. I used your ideas to read and write a
    large number of XML-files. I detected some
    problems in memory management, which also appear
    in the source code provided with this article. I
    thought it would be nice to let you know...
    
    The problems appear if you open the XML-file
    several times. Change the code in
    CXMLSerializationDoc::OnOpenDocument as follows:
    
    for(int i=0; i<10000; i++)
    {
    	CXMLArchive xmlArchive(lpszPathName, CArchive::load, NULL, this);
    	Serialize(xmlArchive);     // load me
    }
    
    Build the program, run, create a new document,
    save it and open it again. Look at the memory
    usage of the program.
    
    Some megabytes are freed when you close the
    document. They were used by the CArrayXML
    instance. The Serialize function should delete
    its contents before reading. So, in the else
    part, add:
    
    // Delete the old contents
    int iOldSize = GetSize();
    for (int iIndex=0; iIndex < iOldSize; iIndex++)
    {
    	delete m_pData[iIndex];
    	m_pData[iIndex] = NULL;
    }
    RemoveAll();
    
    After closing the document, you notice that the
    program uses more memory than before opening it.
    However, closing the program does not show any
    memory leaks. I found out that you forget to free
    a BSTR variable in CXMLArchiveNode::GetDataNode.
    Adding
    
    ::SysFreeString(bstr);
    
    to the end of the function solves the problem.

    • Updated version

      Posted by grahamr (work) on 02/01/2007 06:58am

      Did you see any speed increase from using STL? Also is the latest version available somewhere? Thanks, Graham Reeds.

      Reply
    • thanks

      Posted by mariovespa on 03/01/2005 04:42pm

      The article is now quite old. My new implementation dooes support any longer MFC collections, I have moved to STL (which I recommend). I am happy the article was of use to you. gordon.chisholm@virgilio.it

      Reply
    Reply
  • Don't Forget to write " AfxOleInit()" in InitInstance

    Posted by Legacy on 02/16/2004 12:00am

    Originally posted by: Ajay Sonawane

    Hello there

    Don't Forget to add the line
    AfxOleInit() // enable OLE / COM InitInstace of the application.

    Anyway , Really nice article.Helped to remove difficulties in normal serialization.Thanks

    Reply
  • If I Don't want to override OnCloseDocument() !!!

    Posted by Legacy on 02/13/2004 12:00am

    Originally posted by: Ajay Sonawane

    Hello

    I just created a sample project using your source files.
    I don't want to override OnCloseDocument.( I don't want to
    serialize the objects on OnCloseDocument)
    So I created a object of CXMLArchive like this

    CXMLArchive xmlArchive (lpszPathName,CArchive::store,
    NULL, this);

    But It is giveing me error.

    Is there any other way to do thins?

    Reply
  • Some suggestions

    Posted by Legacy on 07/08/2003 12:00am

    Originally posted by: PerN

    The issue you address is great. I've also been a "victim" of the MFC's serialization mechanism :-)

    Your solution is a nice one, though I'd recosider the design in some ways:


    ---------
    Instead of defining lots of CXMLArchiveNode::DataNode(...) methods in the framework I'd define an (abstract) XMLDataNode class (with pure virtual getData / setData methods) that the framework can call, making the framework independant of the actual data types it can serialize.

    The point is I don't want to fiddle with the framework if I (or someone else) would like to add support for some new types, for instance the various classes that can be based on STL templates, Boost etc.
    ---------
    I would also consider removing all dependencies to MFC. That would mean not using CArchive, but rather defining a more generic archive class that could be derived to XMLArchive / SomeClassUsingCArchive / TextArchive / DBArchive / Whatever. I think it is a shame M$ didn't do this in the first place (ie defining an abstract CArchiveI interface class).
    ----------
    Dependencies to MFC should IMHO only be if the data types require it (but they should not be part of the framework).

    The Serialize(CArchive& ) methods would of course have to be replaced with something else, preferebly a Serialize(GenericArchiveI& ) or similar, but that would (I think) be a good thing - it isn't really a CArchive anyway, not even today, so why pretend it is?

    Which brings me to the next item...
    ---------
    I think inheriting CArchive is unfortunate. CArchive is not intended to be a base class as you can see since its destructor isn't virtual. If the only purpose of deriving is that the Serialize(CArchive&) should work, but it is "your" classes that call/implement it, so why not just let it call something more proper?

    Just some ideas. You address an important issue, feel free to ignore my rambling. :-)


    /Per

    Reply
  • Excellent!!! Can you add some general types support?

    Posted by Legacy on 07/02/2003 12:00am

    Originally posted by: Zhang Kun

    Excellent! Can you add some general types support?
    Such as std::vector, std::list,..., etc.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • Live Event Date: September 17, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this upcoming eSeminar and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds