XMLProfile - a non-MFC, non-STL class to read and write XML profile files






4.88/5 (41 votes)
XMLProfile implements a class to read and write XML profile files that is consistent with the MFC set of profile functions. Conversion functions to allow reading/writing both INI and XML profiles are included. A CWinApp-derived class provides drop-in replacement of the standard MFC profile functio
Introduction
Configuration files have now come full-circle in Microsoft's product roadplan. Originally, .ini files were used to store application-specific data (accessed byGetPrivateProfileXXX()
functions) and
also system data stored in Win.ini (accessed
by GetProfileXXX()
functions). With the
introduction of Win95, developers were encouraged to use the registry instead:
There were good reasons for doing this: applications were storing their .ini files in the Windows directory, or - even worse - dumping application data in Win.ini, and it was impossible to set up multi-user system using a single .ini file.
This of course spawned a whole new cottage industry of registry cleaners, to clean up the registry after misbehaving applications. Soon people were noticing that the registry was being filled up with huge amounts of application data, and began to wonder if this stuff should be put in stand-alone database.
There were also ease-of-use considerations. Even if an application behaved itself and stayed out of HKEY_LOCAL_MACHINE hive, it was still easier to just grab .ini file when you wanted to move application to another machine. Developers noticed that putting a separate .ini file in each user's CSIDL_APPDATA directory (C:\Documents and Settings\<username>\Application Data) was equivalent to using registry HKEY_CURRENT_USER hive. So some applications offered users choice: to use an .ini file, or to use registry.
Now with .Net .config files we are back to a type of .ini file, with a twist: the .config files are encoded in XML. This is probably a good thing, since XML is more accessible in cross-platform situations.
Motivation
Recently one of my clients discussed with me how to make his application more interoperable in his enterprise environment - multiple versions of Windows, Mac, and Linux. He was especially keen on using XML files as a transfer mechanism, but was concerned about the impact on code base, especially some MFC and WTL Windows applications.I quickly sketched out XMLProfile, which would be encoded as XML and require minimal changes to legacy apps. Here is list of features I showed him:
![]() |
No use of MFC or STL - My client wanted to avoid any dependencies that might cause future problems with cross-platform use, and had some specific concerns about use of MFC and STL. Of course, because of cross-platform considerations, this also meant that MSXML could not be used. | |||
![]() |
Use standard XML - This meant that the XML had to be accepted by all major parsing engines, to meet the goal of cross-platform use. | |||
![]() |
Backwards compatibility with ini files - .ini files
have a two-tier structure, with sections containing keys
(unique within a section) and their values.
There is no limit (except practical ones) in XML files for nesting depth,
but I did not want to introduce complexity when it was not needed,
and obviously the legacy applications worked fine with only two levels.
Here are examples of INI and XML profiles:
|
|||
![]() |
Familiar API - This really is the complement to backwards compatibility requirement. Just like data format should be compatible, the API structure should also be intuitive to those familiar with .ini files. | |||
![]() |
Read XML or ini files, write XML and/or ini files - Of course it was a requirement to write XML, but to allow for any unexpected situations, I also wanted to provide for ini input and output. (As it turned out, this was very handy when converting old .ini files). | |||
![]() |
Support for CWinApp Profile functions - To make it as easy as
possible to migrate to XML profiles, I defined
CWinApp -derived class that replaced the CWinApp
profile functions with compatible XML profile functions. This involved
creating a CXMLWinApp class, which is demonstrated in
the XMLWinAppTest demo.
|
|||
![]() |
Support for both Unicode (UTF-16) and ANSI - Support for Unicode was essential for the use of company's applications in international market. |
XMLProfile Concepts and Facilities
Loading and Saving Profiles
These are functions for loading and saving profile files:
Function | Description |
---|---|
BOOL Load(LPCTSTR lpszFileName, ENCODING eEncoding = Default) | Loads the XML profile |
BOOL LoadIni(LPCTSTR lpszFileName, ENCODING eEncoding = Default) | Loads INI profile |
BOOL Save(LPCTSTR lpszFileName, ENCODING eEncoding = Default) | Saves XML profile |
BOOL SaveIni(LPCTSTR lpszIniFile, ENCODING eEncoding = Default) | Saves profile as INI |
The Load()
function loads XML profile data from file,
using second parameter (eEncoding
) to determine
how the file is encoded, and what conversion (if any) should be
applied. The following table shows effect of various combinations
of eEncoding
, compiler preprocessor symbol
_UNICODE
, and
Byte Order Mark (BOM) that is read from the file:
eEncoding | _UNICODE Symbol | BOM | Conversion Applied |
---|---|---|---|
Ansi | Not defined | N/A | None; file assumed to be ANSI |
Ansi | Defined | N/A | Contents converted to Unicode |
Unicode | Not defined | N/A | Contents converted to ANSI |
Unicode | Defined | N/A | None; file assumed to be Unicode |
Default | Not defined | Not present | None; file assumed to be ANSI |
Default | Not defined | Present | Contents converted to ANSI |
Default | Defined | Not present | Contents converted to Unicode |
Default | Defined | Present | None; file assumed to be Unicode |
The Save()
function is somewhat simpler in its handling
of the Default
encoding option. When saving,
Default
simply means use Unicode encoding if
_UNICODE
is defined, otherwise use ANSI.
Low-Level Functions
These are low-level functions ofCXmlProfile
:
Function | Description |
---|---|
void Close(BOOL bSave) | Close profile file |
void DeleteContents() | Empties data contents of section and key lists, frees memory |
void Dump() | Dumps contents of profile via OutputDebugString() |
CXmlKeyListElement * FindKey(LPCTSTR lpszSectionName, LPCTSTR lpszName) | FindKey retrieves key named lpszName in section named lpszSectionName |
CXmlKeyListElement * FindKey(CXmlSectionListElement *pSection, LPCTSTR lpszName) | FindKey retrieves key named lpszName in section specified by pSection |
CXmlSectionListElement * FindSection(LPCTSTR lpszName) | FindSection retrieves section named lpszName |
CWTLString GetKey(LPCTSTR lpszSectionName, LPCTSTR lpszKeyName, LPCTSTR lpszDefault) | GetKey retrieves key named lpszName in section specified by pSection |
UINT GetKeys(LPCTSTR lpszSectionName, CXmlProfileKey **ppKeyBuffer) | GetKeys retrieves all the keys in a section |
UINT GetNumKeys(LPCTSTR lpszSectionName) | GetNumKeys retrieves the number of keys in a section |
UINT GetNumSections() | GetNumSections retrieves the number of sections in the profile |
BOOL GetReadOnly(CXmlKeyListElement *pKey) | GetReadOnly retrieves the readonly status of a key |
BOOL GetReadOnly(CXmlSectionListElement *pSection) | GetReadOnly retrieves the readonly status of a section |
UINT GetSections(CXmlProfileSectionEntry **ppSSectionArray) | GetSections retrieves all the sections in the profile |
BOOL IsFileOpen() | IsFileOpen checks if profile file is open |
BOOL IsKey(LPCTSTR lpszSectionName, LPCTSTR lpszKeyName) | IsKey checks if key exists |
BOOL IsSection(LPCTSTR lpszSection) | IsSection checks if section exists |
CXmlKeyListElement * SetKey(LPCTSTR lpszSectionName, LPCTSTR lpszKeyName, LPCTSTR lpszValue, BOOL bReadOnly = FALSE) | SetKey sets the value lpszValue in the key named lpszKeyName in section named lpszSectionName |
CXmlKeyListElement * SetKey(CXmlSectionListElement *pSection, LPCTSTR lpszKeyName, LPCTSTR lpszValue, BOOL bReadOnly = FALSE) | SetKey sets the value lpszValue in the key named lpszKeyName in section specified by pSection |
BOOL SetKeys(LPCTSTR lpszSectionName, CXmlProfileKey *pKeyBuffer, UINT nKeys) | SetKeys creates/updates keys in a section |
void SetReadOnly(CXmlSectionListElement *pSection, BOOL bReadOnly) | SetReadOnly sets the readonly status of a section |
void SetReadOnly(CXmlKeyListElement *pKey, BOOL bReadOnly) | SetReadOnly sets the readonly status of a key |
BOOL Validate() | Validates contents of profile |
MFC-Compatible Functions
These are MFC-Compatible functions:
Function | Description |
---|---|
BOOL GetProfileBinary(LPCTSTR lpszSection, LPCTSTR lpszEntry, BYTE** ppData, UINT* pBytes) | Retrieve binary data from an entry within a specified section of the profile |
BOOL WriteProfileBinary(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPBYTE pData, UINT nBytes) | Writes binary data into an entry within a specified section of the profile |
UINT GetProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry, int nDefault) | Retrieves an integer value from an entry within a specified section of the profile |
BOOL WriteProfileInt(LPCTSTR lpszSection, LPCTSTR lpszEntry, int nValue) | Writes an integer value into an entry within a specified section of the profile |
CWTLString GetProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszDefault) | Retrieves a string from an entry within a specified section of the profile |
BOOL WriteProfileString(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszValue) | Writes a string into an entry within a specified section of the profile |
XML Tags
The tags used in XML profile files may be changed as required:
CXmlProfile String Variable | Default Value |
---|---|
m_strKeyTag | "key" |
m_strNameTag | "name" (used for both sections and keys) |
m_strProfileTag | "profile" |
m_strReadOnlyTag | "readonly" |
m_strSectionTag | "section" |
m_strValueTag | "value" |
Implementation Notes
XMLProfile may be compiled without any dependence on MFC, by simply commenting out line#include "stdafx.h" // use of MFC is optional, unless CXmlWinApp is being usedin files XMLProfile.cpp and XMLParser.cpp. (If using
CXmlWinApp
class, then you should include this line).
Because XMLProfile makes heavy use of strings, I had to find
replacement for MFC's CString
class. I found such a class in
WTL, and renamed it to
CWTLString
to avoid any conflicts. When MFC is being used,
there is #define
to redefine CWTLString
to CString
.
The other major problem was what to do about list handling. I wanted
to avoid using both MFC's and STL's list classes, because of some
configuration issues with my client's code. A colleague suggested I look
at the
doubly-linked list class written by Sam Buss, which proved
to be a very good choice. I use this both in
CXmlProfile
and CXmlParser
.
XMLProfile Demo
Here is what the XMLProfile demo looks like:
The Convert dialog allows you to convert back and forth between INI files and XML files:
The Test dialog exercises XMLProfile functions with automated tests:
How to use
Step 1 - Add Files
To integrate XMLProfile into your app, you first need to add following files to your project:
- XMLProfile.cpp
- XMLProfile.h
- XMLParser.cpp
- XMLParser.h
- CLinkedList.h
- wtlstring.h
If you are not using MFC, you can set the .cpp files to Not using precompiled header in Visual Studio. Otherwise, you will get error
fatal error C1010: unexpected end of file while looking for precompiled header directive
If you want to use the CWinApp
-based XML profile
functions, you should also include:
- XMLWinApp.cpp
- XMLWinApp.h
CXmlWinApp
requires that you enable precompiled headers
for XMLProfile.cpp and XMLParser.cpp.
Step 2 - Add Header File to Your Source Module
In the module where you want to use XMLProfile, include header file XMLProfile.h.
Step 3 - Add Code
See XMLProfileTestDlg.cpp and Test.cpp for examples of how to use.
Using XMLProfile with MFC
The code used in XMLProfile may be compiled with or without MFC. To compile without MFC, make sure the line #include "stdafx.h" is commented out in files XmlParser.cpp and XmlProfile.cpp, and set both of these files to Not using precompiled header in Visual Studio.To compile with MFC, make sure the line #include "stdafx.h" is not commented out in files XmlParser.cpp and XmlProfile.cpp, and set both of these files to Use precompiled header in Visual Studio.
References
- XResFile - Files Stored in Resources
- XListBox - Owner-draw CListBox with selectable text and background colors
- XButtonXP - an XP-aware pushbutton that can also toggle and display an icon
- ExecApp, ExecRegisteredApp, and LookupRegisteredApp - non-MFC functions to execute an application
- XEditPrompt - CEdit-derived control with web-like prompt
- XFileDialog - Customizing CFileDialog
- XHyperLink - yet another hyperlink control
Useful Links
More Articles
Here are some other articles on CodeProject that deal with profile and configuration files:- Read/Write XML files, Config files, INI files, or the Registry By Alvaro Mendez
- XML Application Profile Class By John Simmons / outlaw programmer
- CXMLProfile - Easy XML Profiles for applications By Emilio Guijarro
- Application Settings the .NET way. INI, Registry, or XML By Chad Z. Hower aka Kudzu
- Managing configuration settings persistence in .NET applications By Alberto Venditti
- How to read and write an INI File By Aisha Ikram
- INI Files By gajatko
- A Complete Win32 INI File Utility Class By wilsone8
- An INI file handling class using C# By BLaZiNiX
- Using Unicode in INI files By Mana#
- Convert INI file to XML By Loki
- Cross-platform INI configuration files (Win32/Un*x, MBCS/Unicode) By brofield
- A little class to Read Ini File By xiaohe521
- CIniFile Class for C++ - A robust cross platform INI file class By Todd Davis
- CIniFile By Cabadam
- INIFile Class By Jack Schitt
Revision History
Version 1.0 - 2008 July 2
- Initial public release
Usage
This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.