Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

How to Hold Your Objects and Application Settings in a Config XML File, a Non Intrusive Way

4.00/5 (1 vote)
24 Oct 2013CPOL4 min read 13K   137  
Effortless configuration file added to your existing code

Introduction

One of the always present points in a non trivial program is the need to keep some of its internal data among executions. Normally, the variables must need an initial value for code to work, and must accept modifications, both externally, or by the program itself. This is called persistence, and is sometimes associated with another property called serialization.

One of the evidences of how difficult it is to do this in the right way is the bunch of methods used, both good and bad. In MSDOS times, some well known guys used the global environment variables to satisfy their local configuration needs, overloading this resource. Windows registry, one of the Windows weak points is used and abused also for this task.

Background

Actually, all C variables are composed of sets of a few pure types. We can take advantage of the ability given by C++ templates to manage these types. This template set provides you, as developer:

  • Initialization: The config file is filled at startup with the compile time assigned values, once created. You can call this "zero user configuration".
  • Modification: You can use a text or XML editor to modify any value on file, it's "human readable".
  • Fault tolerancy: As long as the XML file keeps coherent, you can remove whole or pieces of this config file. The effect is that the removed parts will be restored with the default values.
  • Unorder tolerancy: You can change the order of items they will be read correctly.
  • Non intrusive: This means we don't need to derive saved classes from nothing, so their memory structure remains untouched.

Using the Code

I've included two projects, both Visual Studio and code::blocks, which is my main IDE so you can try the example both in Windows and Linux.

This code must be linked against expat library. The source package includes all that is necessary. In a production environment, you need really a few files of package, but I've included all, as well as a code::blocks (cbp) and Visual Studio (dsw) project.

You also have an XP win32 simple executable (and the expat DLL) so that you can test the config file creation at once.

You must tell about the parts you want to make persistent in your objects this way.

highlighted text is what you need to include in your existing code to keep an unbreakable XML config file for your existing program.

C++
/* This is your include file (yourinclude.h) */

#include "xmlconf.h"          /* library access */

struct RootStruct             /* Declared struct because of C/C++ compatibility*/
{ int             storedInt;
  LeaveStruct     aBranch[5];
  char *          ptr;        /* Not persistent variable */ 

/* Variables on object you want to keep trach of in your config file, 
must add this to made this object capable of persistence
 */
  XMLCONFSTART           
    XMLCONFITEM( storedInt ); /* Pure type, persistence rules yet written by library */    
    XMLCONFARRY( aBranch   ); /* This is one type previously declared by you. Can be nested */ 
  XMLCONFEND

};

This will be your modified main file:

C++
	include "yourinclude.h"
	
	/*  These are the global variables we need to keep track on the config file */

  LeaveStruct leave;
   RootStruct root;
   
          int globalInteger;
        short globalArray[]= {1,2,3,4,5,6,7,8,9};
unsigned char globalBytes[]= {10,20,30,40,50,60,70,80,90};

/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
 *   Below lines does the magic. this maintain the fault tolerant config file
 *  Actually this is the piece of code you must add to maintain your config file.
 */

XMLCONFDECLARESTART( "testcfg.xml" ) // This defines the config file name
  XMLCONFITEM( leave         );      // This keeps your "leave" named object in the config file
  XMLCONFITEM( root          );      // This keeps your nested object "root" in the config file
  XMLCONFITEM( globalInteger );      // This one and below keeps global variables persistent
  XMLCONFARRY( globalArray   );      // Global array added to the config file
  XMLCONFARRY( globalBytes   );      // We can also add a pure type arrays to the config file
XMLCONFDECLAREEND


/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 * Code starts here
 */

int main()
{ leave.aInteger[0]++; // Persistent changes, config file keeps track of this
  root.storedInt++;    // Increases on every execution of program

  char p[10];


  printf( "Config stored values -> (%s) %d %d\n"
        , root.aBranch[0].aString
        , leave.aInteger[0]
        , root.storedInt );
        
        
  return( 0 );
}

This is the config file that the templates, and libstdc++ keeps for the provided example:

XML
<?xml version="1.0" ?>
<config name="testcfg.xml">
  <LeaveStruct name="leave">
    <byte aByte="0"/>
    <int aInteger="28,0,0,0,0"/>
    <str aString=""/>
  </LeaveStruct>
  <RootStruct name="root">
    <int storedInt="1262"/>
    <LeaveStruct name="aBranch" idx="0">
      <byte aByte="0"/>
      <int aInteger="0,0,0,0,0"/>
      <str aString="Hello, world hola"/>
    </LeaveStruct>
    <LeaveStruct name="aBranch" idx="1">
      <byte aByte="0"/>
      <int aInteger="0,0,0,0,0"/>
      <str aString="These are"/>
    </LeaveStruct>
    <LeaveStruct name="aBranch" idx="2">
      <byte aByte="0"/>
      <int aInteger="0,0,0,0,0"/>
      <str aString="the default values"/>
    </LeaveStruct>
    <LeaveStruct name="aBranch" idx="3">
      <byte aByte="0"/>
      <int aInteger="0,0,0,0,0"/>
      <str aString="on the"/>
    </LeaveStruct>
    <LeaveStruct name="aBranch" idx="4">
      <byte aByte="0"/>
      <int aInteger="0,0,0,0,0"/>
      <str aString="config file"/>
    </LeaveStruct>
  </RootStruct>
  <int globalInteger="0"/>
  <short globalArray="1,2,3,4,5,6,7,8,9"/>
  <byte globalBytes="10,20,30,40,50,60,70,80,90"/>
</config>

This is a basic use of this code, but you can also modify the constructor and destructor of an object to make it XML file persistent. Changes on config file will be reflected in the program.

Points of Interest

Templates are real powerful instruments. A smart use of them can avoid big mess, but they have many problems: very optimized code became illegible, and so, difficult to debug or modify. Side effects are a pain and several compilers give sometimes different behaviors and results.

When you compile and strip a program, all "human names" simply disappear, but there is a narrow gateway between our realm and processor realm called typeid (gnu FUNCTION_NAME is also usable) this code takes advantage of this door and other less romantic properties to reflect your program status in an almost human form. Below is the function used that returns your object name in a human readable and portable way:

C++
template < class t > inline const char * typeId( const t & obj )
{ const char * ptr= typeid( obj ).name();

#ifdef __GNUG__
  while( *ptr && (*ptr < 'A' ))
  { ptr++;
  }
#endif

#ifdef _MSC_VER
  const char * needle;
  while( needle= strchr( ptr, ' ' ) )
  { ptr= needle+1;
  }
#endif

  return( ptr );
}

This is the tricky part. I've done an "optimization for ease of use" The templates arrange a path of codlets in a tree fashion. In read, metadata sequentially given by expat library is tested against the codlets, to reach their right place. For writing, at the end of the program or in object disposal, the codlet tree is sequentially stepped again, writing each value. These codlets are capable of read and writing, so is only necessary one of them for each variable in our object. We get all this stuff working only by declaring a line each variable to be saved in object

History

As commented, I've searched for a portable way to avoid those setup prerequisites on a new installation, and this little piece of code is the result.

It may be not difficult to modify the code to use any tree-type data container, like a database, to store this config data.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)