Introduction
You've probably worked with programming languages that allowed you to inspect an object to determine what attributes make up the object at runtime (FoxPro, Perl etc). This feature is very handy if you need to write generic serialization routines. If you are programming in C++ then you probably also know that this type of introspection is missing.
Using the code
I've tried to create a series of macros and classes which will allow you to expose the attributes of your classes at runtime, with little change to your existing code. To accomplish this, all you need to do is inherit your class from CExposeAttributes
and in the constructor of your class add one line for every attribute. Of course, I am making a big assumption. The assumption is that you have implemented Get
/Set
methods for all of your attributes. CExposeAttributes
works by maintaining a list of function pointers to those Get
/Set
methods. This exposes the class' attributes, but only through the public methods. In addition, I have only implemented support for the following data types: long
, double
, CString
, CGuid
, bool
and LPCTSTR
. The Get
/Set
methods must conform to the following function signatures:
long:
YourSetFunction(long l);
long YourGetFunction() const;
double:
YourSetFunction(double l);
double YourGetFunction() const;
bool:
YourSetFunction(bool l);
bool YourGetFunction() const;
LPCTSTR:
YourSetFunction(LPCTSTR l);
LPCTSTR YourGetFunction() const;
CString:
YourSetFunction(const CString& l);
CString YourGetFunction() const;
CGuid:
YourSetFunction(const CGuid& l);
CGuid YourGetFunction() const;
If you adhere to some other standard of Get
/Set
methods, you will need to modify or add support for the function pointer typedef
's in CGetAttribute
and CSetAttribute
. Instructions are in the code (serialize.h) for adding support for additional data types.
class CSampleAttribute : public CXMLSerialization,
CExposeAttributes<csampleattribute>
{
public:
CSampleAttribute();
virtual ~CSampleAttribute();
void SetName(LPCTSTR sName){m_sName=sName;};
LPCTSTR GetName() const {return m_sName;};
void SetSize(double dSize){m_dSize=dSize;};
double GetSize() const {return m_dSize;};
void SetLength(long lLength){m_lLength=lLength;};
long GetLength() const {return m_lLength;};
void SetID(const CGuid& oID){m_ID=oID;};
CGuid GetID() const {return m_ID;};
bool Save(MSXML2::IXMLDOMElementPtr& pParentElement);
bool Load(MSXML2::IXMLDOMElementPtr& pElement,const CGuid& oID);
private:
CString m_sName;
double m_dSize;
long m_lLength;
CGuid m_ID;
};
</csampleattribute>
Assuming you have your Get
/Set
methods defined, you are ready to declare the attributes. To do this, in your class' constructor use one of the following macros:
CSampleAttribute::CSampleAttribute()
{
SET_CGUID_ATTRIBUTE(CSampleAttribute,"ID",SetID,GetID);
SET_LPCTSTR_ATTRIBUTE(CSampleAttribute,"Name",SetName,GetName);
SET_LONG_ATTRIBUTE(CSampleAttribute,"Length",SetLength,GetLength);
SET_DOUBLE_ATTRIBUTE(CSampleAttribute,"Size",SetSize,GetSize);
}
That's it! Your class now has a couple of methods that will allow you to enumerate all of it's attributes. Calling GetGetAttributes
or GetSetAttributes
will return you a list of CGetAttribute
or CSetAttribute
. You can write serialization classes that can enumerate through any object and read/write its attributes. I have included a sample class called CXMLSerialization
. If you inherit your class from CXMLSerialization
then you will inherit two methods:
virtual bool Save(MSXML2::IXMLDOMElementPtr& pParentElement) =0;
virtual bool Load(MSXML2::IXMLDOMElementPtr& pElement, const CGuid& oID) =0;
Override these methods to allow your class to write its attributes to an XML element.
bool CSampleAttribute::Save(MSXML2::IXMLDOMElementPtr& pParentElement)
{
ASSERT(pParentElement!=NULL);
if (GetDirty()== false)
return true;
CString sXPATH;
CXMLAttribute<csampleattribute> oAttribute;
sXPATH.Format("%s[@%s='%s']","SampleAttribute",
"ID",GetID().CStrValUCase());
MSXML2::IXMLDOMElementPtr pCur =
pParentElement->selectSingleNode((LPCTSTR)sXPATH);
if (pCur == NULL)
{
pCur =
pParentElement->GetownerDocument()->createElement("SampleAttribute");
pParentElement->appendChild(pCur);
}
oAttribute.Save(pCur,this,this);
SetDirty(false);
return true;
}
bool CSampleAttribute::Load
(MSXML2::IXMLDOMElementPtr& pElement,const CGuid& oID)
{
ASSERT(pElement!=NULL);
CXMLAttribute<csampleattribute> oAttribute;
oAttribute.Load(pElement,this,this);
SetDirty(false);
return true;
}
</csampleattribute></csampleattribute>
CXMLAttribute
knows how to load and save all types of attributes to and from XML. A similar class could easily be created to read/write to a relational database.