Click here to Skip to main content
Email Password   helpLost your password?

Introduction

Ever wonder what those Microsoft MFC message mapping macros did under the covers? It was always black magic to me - how clicking a button caused a function to be called. Luckily for the curious, Visual Studio lets you sleuth through the macros by using "Go to Definition" from the context menu.

I recently had a need to convert a structure to XML and vice versa in order to allow a web server to send TCP/IP messages to my server process - the messages would be formatted in XML, but the server would of course need to have access to the parsed XML elements in some sort of structure. There are a lot of XML parsers out there, including ActiveX controls - and so I make a disclaimer here that this is not intended to be a full featured parser. Our XML needs were very simple, and this is a simple solution.

But the purpose of the article is not really about XML parsing - it is about mapping the members of a class - either data or function - to a list, which contains information about the members, and associations to some function, so you can do something useful with the class members without having to write a bunch of code for each of your derived classes.

To better illustrate the point, take a look at the Microsoft message mapping macros (afxwin.h). There are four basic macro functions at work here.

We will use the following code as an example:

BEGIN_MESSAGE_MAP( CMyClass, CFrameWnd )
   ON_MESSAGE(CM_SOCKET_EVENT, OnSocketEvent)
   ON_MESSAGE(CM_FOLDER_CHANGED, OnFolderChanged )
END_MESSAGE_MAP()

That's basically all there is. In our XML mapping example, we use the mapping macros to create structures that describe the member variables. This initial version supports the following basic types, but can easily be extended for support of other atomic data types like float, etc.

Integers

Strings (CStrings, but could be ported to use any string type)

Classes - Must be derived from the CXMLMessage class

New Features for Version 1.01 (November 16, 2004)

New Features for Version 1.2 (November 18, 2004)

Background

Familiarity with Windows message handling, using the Windows macros described above.

Using the code

The XML mapping macros in this example are very easy to use. Exporting a class to XML is done using the Deflate() function, and Importing from XML is done using the Inflate() function.

Here is an example of the XML map, which is in the CPP file for the derived class.

Note: The syntax for Inflate and Deflate has changed for V1.01 - much simpler to use.

BEGIN_XML_MAP( CMsgDestinationInfo, _T("destinationInfo") )
   // OR with XML_POINTER, XML_ARRAY as necessary

   XML_ELEMENT_DTYPE( _T("destination"),  m_destination,  XML_STRING )
   XML_ELEMENT_DTYPE( _T("filePathRoot"), m_filePathRoot, XML_STRING )
END_XML_MAP()

Version 1.2 now supports derived classes. Use the BEGIN_XMP_MAP2 macro for these. You can derive as many levels as you need to. Each derived class will include the XML mapped members from its base class. In this example, CMsgFTPDestinationInfo will have the members "destination" and "filePathRoot" included from CMsgDestinationInfo.

BEGIN_XML_MAP2( CMsgFTPDestinationInfo, _T("ftpDestinationInfo"), 
                                           CMsgDestinationInfo )
   XML_ELEMENT_DTYPE( _T("username"),  m_username,  XML_STRING )  
   XML_ELEMENT_DTYPE( _T("password"),  m_password,  XML_STRING )
END_XML_MAP()

And the header file for our class - with map declaration. Note: constructors and destructors are automatically provided by the macros. If you need special behavior here, override Initialize() or Cleanup() in your derived class.

class CMsgDestinationInfo : public CXMLMessage
{
   public:
  
   CString m_destination;
   CString m_filePathRoot;

   DECLARE_XML_MAP(CMsgDestinationInfo);
};

Or in the case of a derived class:

class CMsgFTPDestinationInfo : public CMsgDestinationInfo 
{
   public:
  
   CString m_destination;
   CString m_filePathRoot;

   DECLARE_XML_MAP(CMsgFTPDestinationInfo );
};

I'll spare you the initialization of the class members, but assuming that is done, here is how you generate the XML:

// Our XML Message object

CMsgFTPDestinationInfo message1;

int size = 0;
TCHAR* ptr = 0;

// Deflate the object ( save as XML )

if ( message1.Deflate( ptr, size ) != 0 )
   return 1; // Failed if ( message1.Deflate( ptr, size ) != 0 )


// ptr & size are now valid. ptr is deleted when message1 destructs.

And here is how you load the class members from an XML buffer (read from a file, socket, etc.):

// Inflate a new object from the XML buffer

CMsgFTPDestinationInfo message2;
if ( message2.Inflate( ptr, size ) != 0 )
    return 1; // Failed

Generating schema definition files is easy:

CMsgFTPDestinationInfo message3;
if ( message3.GenerateDTD() != 0 ) // Document Type Definitions

    return 1; 
if ( message3.GenerateXSD() != 0 ) // XML Schema Definitions

    return 1; // Failed

Points of Interest

I enjoy doing things you aren't supposed to be able to do - like manipulating and recasting pointers. Everything that is done in software is done by interpreting the meaning of a block of memory. And trying to understand how the compiler interprets pointers and what expects to find in a block of memory is always a fun challenge.

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralReference to XML_ATTRIBUTE
c-sharp
4:54 28 Apr '06  
In XMLMacros.h, there are references made to how to assign Attributes to Elements [commented section in the bottom portion of the file]. The Define for XML_ATTRIBUTE is present but there are no other references in the body of the sources that I can find that _use_ XML_ATTRIBUTE. The comments led me to believe the functionality was available, but I do not believe this to be the case.

Can you please clarify/correct my confusion and/or provide us with the updated sources??

Thanks!


Scott
GeneralRe: Reference to XML_ATTRIBUTE
Steve Johnson (Sven)
5:14 28 Apr '06  
Hi Scott,

It's been a year and a half since I wrote this, so I can only guess what my former self was thinking. There never was code to support XML_ATTRIBUTE - just the place holder as a bitmask, but as I ( vaguely ) recall the intention was to eventually support the XML Attribute ( vs. Element ) syntax in the generated .xml file itself. Anything that can be represented as an Attribute can also be represented as an Element, and so I never pursued this optional representation.

If you feel inspired, implement this and let me know how it turned out!

Steve
GeneralBug in CXMLMessage::DeflateInteger(...) ?
OKN
1:02 4 Nov '05  
An interesting idea to use macros for XML handling in C++, thanks for presenting it. I made some tests with the code, played with another simple data structure, and produced some XML output ("deflating" the data structure). Inspecting the XML output I found a problem with integers. After some debugging I got into
CXMLMessage::DeflateInteger
to the line (nice casts, indeed Wink )
intVal = (int) *((char*) this + a_entry->m_offset ); which causes the problem (wrong int values in the XML; e.g. 1234 mutates to -46). I changed the line into
intVal = *(int*)( (char*)this + a_entry->m_offset ); what solved my problem.
OKN
GeneralRe: Bug in CXMLMessage::DeflateInteger(...) ?
Steve Johnson (Sven)
6:37 4 Nov '05  

A bug? Impossible! OMG

Thanks.
GeneralGreat for serialization
CasualT
13:33 30 May '05  
Great job, Steve. I found this article really informative, not because I was interested in XML, but because I was looking for an example of how to keep a table of class members for serialization purposes. Your macros are just what I was looking for!
GeneralHow to use with Collections
c-sharp
6:32 27 May '05  
I'm interested in understanding if this approach can be used for embeded collections (data members of eith CArray, CList OR fixed length arrays of structures). It isn't obvious to me how a compile-time mapping can accomodate such a problem.

Thanks!

Scott
GeneralRe: How to use with Collections
Steve Johnson (Sven)
7:14 27 May '05  
Hi Scott,

Yes and No. All arrays supported are implemented using CArray. You can use CArray for class pointers, but NOT CArray of classes. You could probably extend the code to do this. ( No guarantees, but give it a try ).

Look in CXMLMessage::InflateClass(). Under the array section, you will see where an array of class objects are not supported, ( but array of class pointers are ). In this section you could try:

CArray *array = ( CArray* )((char*) this + a_entry->m_offset );
while(1)
{
// Call the create function to return a pointer to the derived class.
// This create function is declared and
// defined by the macros DECLARE_XML_MAP and BEGIN_XML_MAP.

CXFUNC func = a_entry->m_fpCreate;
CXMLMessage* newClass = (*func)();

array->Add( *newClass ); // Now add class instead of class pointer.

// Recurse for this class
if ((rc = newClass->Inflate( a_buffer, a_limit, a_size, tag.GetBuffer()) != 0 ))
return rc;

// Peek ahead at next element - same type?
if ( CheckTag( a_buffer, a_limit, a_size, tag ) != 0 )
{
// No, end of array
return 0;
}
}

You will also need the inverse for CXMLMessage::DeflateClass()
Also check the mapping macros to allow for non-pointers to be
declared for class arrays. I will let you figure it out, but here is the
current macro, which as you can see forces the pointer attribute:

// Member variable is a class. Only class pointers are supported for arrays.
#define XML_ELEMENT_CLASS( token, memberElement, classType, flags ) \
{ token, MEMBER_OFFSET( memberElement ), \
flags | XML_CLASS | ((flags & XML_ARRAY ) ? XML_POINTER : 0 ), \
(CXFUNC) (reinterpret_cast< CXMLMessage* (*)() > \
(classType::newInstance)) }, \

Here is sample code for CArray of class pointers. You should be able to
figure out how to change for CArray of class.

// header file:
class CGroupList : public CXMLMessage
{
public:

CArray m_groups;

DECLARE_XML_MAP(CGroupList);
};

// source file:
BEGIN_XML_MAP( CGroupList, _T("CGroupList") )
XML_ELEMENT_CLASS( _T("group"), m_groups, CGroup, XML_ARRAY | XML_OCCUR_0M )
END_XML_MAP()

You would want to change the macro statement so this would have to read as follows if you
were using class pointers, and unchanged ( as above ) if you were using class.

XML_ELEMENT_CLASS( _T("group"), m_groups, CGroup, XML_ARRAY | XML_POINTER | XML_OCCUR_0M )

Good Luck!

Steve

GeneralRe: How to use with Collections
Steve Johnson (Sven)
7:28 27 May '05  
( Sorry, previous reply had XML tagging enabled, which removed important stuff from the post. )

Hi Scott,

Yes and No. All arrays supported are implemented using CArray. You can use CArray for class pointers, but NOT CArray of classes. You could probably extend the code to do this. ( No guarantees, but give it a try ).

Look in CXMLMessage::InflateClass(). Under the array section, you will see where an array of class objects are not supported, ( but array of class pointers are ). In this section you could try:

CArray<CXMLMessage> *array = ( CArray<CXMLMessage>* )((char*) this + a_entry->m_offset );
while(1)
   {
      // Call the create function to return a pointer to the derived class.
      // This create function is declared and
      // defined by the macros DECLARE_XML_MAP and BEGIN_XML_MAP.

      CXFUNC func = a_entry->m_fpCreate;
      CXMLMessage* newClass = (*func)();

      array->Add( *newClass );   // Now add class instead of class pointer.

      // Recurse for this class
      if ((rc = newClass->Inflate( a_buffer, a_limit, a_size, tag.GetBuffer()) != 0 ))
                         return rc;

      // Peek ahead at next element - same type?
      if ( CheckTag( a_buffer, a_limit, a_size, tag ) != 0 )
      {
     // No, end of array
     return 0;
      }
}

You will also need the inverse for CXMLMessage::DeflateClass()
Also check the mapping macros to allow for non-pointers to be
declared for class arrays. I will let you figure it out, but here is the
current macro, which as you can see forces the pointer attribute:

// Member variable is a class. Only class pointers are supported for arrays.
#define XML_ELEMENT_CLASS( token, memberElement, classType, flags ) \
   { token, MEMBER_OFFSET( memberElement ), \
      flags | XML_CLASS | ((flags & XML_ARRAY ) ? XML_POINTER : 0 ), \
      (CXFUNC) (reinterpret_cast< CXMLMessage* (*)() > \
      (classType::newInstance)) },     \

Here is sample code for CArray of class pointers. You should be able to
figure out how to change for CArray of class.

// header file:
class CGroupList : public CXMLMessage
{
   public:

   CArray<CGroup*,CGroup*> m_groups;

   DECLARE_XML_MAP(CGroupList);
};

// source file:
BEGIN_XML_MAP( CGroupList, _T("CGroupList") )
   XML_ELEMENT_CLASS( _T("group"), m_groups, CGroup, XML_ARRAY | XML_OCCUR_0M )
END_XML_MAP()

You would want to change the macro statement so this would have to read as follows if you
were using class pointers, and unchanged ( as above ) if you were using class.

   XML_ELEMENT_CLASS( _T("group"), m_groups, CGroup, XML_ARRAY | XML_POINTER | XML_OCCUR_0M )

Good Luck!

Steve

GeneralRe: How to use with Collections
Steve Johnson (Sven)
7:39 27 May '05  

Scott,

Sorry, disregard the previous suggestion. I now remember why it can't work.

The cast:

CArray<CXMLMessage> *array = ( CArray<CXMLMessage>* )((char*) this + a_entry->m_offset );

won't work, because it is a cast to the BASE class, which is smaller
than the derived class you are trying to inflate. Casting to CArray of class pointers
works because the size of a pointer is the same regardless of the derived class type.

Just use a CArray of class pointers and you'll be all set.

Steve

GeneralImprovements for multiple inheritance
KenGuru
0:03 18 Nov '04  
Hi!

I've made some improvements to use multiple inheritance. Example:

class CXMLVehicle : public CXMLMessage
{
int m_iHorsePower;
CString m_strType;

//Colors for RGB
int m_iClr_Red;
int m_iClr_Gren;
int m_iClr_Blue;
};

class CXMLSportsCar : public CXMLVehicle
{
// ... any other members here
}

there was no simple way to Deflate the members
from the base class (CXMLVehicle)

Add the following code:

in XMLMacros.h
#define MAP_CXMLMessage
#define IMPLEMENT_XML_MAP( derived, base, tagExpr) \
BEGIN_XML_MAP( ##derived, _T(tagExpr)) \
MAP_##base \
MAP_##derived \
END_XML_MAP()


Instead of the BEGIN_XML_MAP(...) and END_XML_MAP() macros use the IMPLEMENT_XML_MAP(...) macro

in your headerfile write a #define statement like
#define MAP_YourClassName \
XML_ELEMENT_DTYPE( ... )\
XML_ELEMENT_CLASS( ... )\

to define your map. This macro is used by the IMPLEMENT_XMP_MAL macro


GeneralRe: Improvements for multiple inheritance
Steve Johnson (Sven)
11:05 18 Nov '04  
Thanks for the suggestion! I was actually just finishing up the latest version which supports derived classes. This is version 1.2 which should be posted sometime today. The C++ header still looks just as you have suggested for CXMLSportsCar. The only change to the message map is as follows:

BEGIN_XML_MAP( CXMLVehicle, _T("Vehicle"))
   XML_ELEMENT_DTYPE( ...... )
END_XML_MAP

and for the derived class:

BEGIN_XML_MAP2( CXMLSportsCar, _T("SportsCar"), CXMLVehicle)
   XML_ELEMENT_DTYPE( ...... )
END_XML_MAP

The Deflate, Inflate, and Copy functions automatically process the members of the base class. This is not multiple inheritence, but you can derive linearly as many times as you like. Base class elements are processed recursively.


GeneralThis is What I Want
sudhir mangla
21:36 15 Nov '04  
This is Exactly What I was looking for past some days.

thanks

Sudhir Mangla

http://Programmerworld.net
(Free books , articles and Source Code)
GeneralNice, look what I did
Anonymous
7:31 11 Nov '04  
// macros for defining actor's class members
# define _CLASSMEMBER(clas,member,fieldtype,count,flags) \
{fieldtype, #member, count, flags, offsetof(clas,member)}

# define DEFINE_CLASSMEMBER(clas,member,ftype) \
_CLASSMEMBER(clas, member, ftype, 1, 0)


Last Updated 22 Nov 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010