XML Serialization for MFC
- 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.
- 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.
- Archived data cannot be edited, understood, and changed, except with the associated application.
- Archived data cannot be exchanged across platforms.
- 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):
- Include the files XMLArchive.cpp and XMLArchive.hpp into your project.
- Copy the supplied stdafx.h fragment into your stdafx.h.
- Override your CDocument derived class's OnSaveDocument to use CXMLArchive instead of the normal CArchive.
- 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 KbDownload source - 6 Kb

Comments
Any idea of how can I add cptrlist support?
Posted by Mythos on 09/17/2005 06:29amI'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.-
ReplyRe: Any idea of how can I add cptrlist support?
Posted by peterburn on 11/09/2005 09:12pmIn the main loading loop, use m_archivePtr->m_childIndex rather than m_childIndex. Remove m_childIndexBefore.
ReplySome fixes
Posted by GuidoB on 02/28/2005 09:32amThanks 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.-
-
ReplyUpdated version
Posted by grahamr (work) on 02/01/2007 06:58amDid you see any speed increase from using STL? Also is the latest version available somewhere? Thanks, Graham Reeds.
Replythanks
Posted by mariovespa on 03/01/2005 04:42pmThe 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
ReplyDon't Forget to write " AfxOleInit()" in InitInstance
Posted by Legacy on 02/16/2004 12:00amOriginally 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
ReplyIf I Don't want to override OnCloseDocument() !!!
Posted by Legacy on 02/13/2004 12:00amOriginally 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?
ReplySome suggestions
Posted by Legacy on 07/08/2003 12:00amOriginally 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. :-)
Reply/Per
Excellent!!! Can you add some general types support?
Posted by Legacy on 07/02/2003 12:00amOriginally posted by: Zhang Kun
Excellent! Can you add some general types support?
Such as std::vector, std::list,..., etc.
Reply