Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

INI Reader / Writer Class for MFC and ANSI C++ ( Windows Linux )

0.00/5 (No votes)
10 May 2011 1  
Object based INI file editing in Windows and Linux

Introduction

This is a partial re-write of the code written in 2009. Originally (2005), the data structures underlying the CIniFile object were MFC CList objects. This was extremely inefficient when dealing with huge ini files. People contacted me about a version which would work in Ansi C++\STL. After posting a version which would compile without MFC under an Ansi C++\STL compiler, it became quite obvious that there should be another revision which would get rid of the slow (MFC)CList or (STL)std::list. After the re-write in 2009, the code was modified to utilize std::map which greatly improved performance and efficiency. However, there was still an issue with how efficiently the underlying data was being stored. Although the speed of the 2009 version was infinitely better than the 2005 original, there was wasted space used by the std::map key since each object CIniSection and CIniKey store their own section name and key name respectively. This version replaces the existing std::map<std::string,CIniSection*> and std::map<std::string,CIniKey*> with std::set<CIniSection*> and std::set<CIniKey*> respectively. This modification allows the section and key names to be stored once which greatly reduces memory usage on large INI files, while keeping the O(log(n)) efficiency. When used in an MFC project CStrings should either be cast to (LPCTSTR) or the GetString() member of CString should be used when passing CStrings to CIniFile functions. The CIniFile class stores strings as std::wstring or std::string depending on whether you are compiling your MFC project Unicode or Multibyte. Since CString performs horribly and is not C++ standard the use of CString internally has been removed in 2009. The current CIniFile class which uses standard STL strings works fine in MFC projects. The ability to use std::stream with the CIniFile object was recently added per request of a user. To further improve efficiency, many function calls were modified to pass by const std::string& or const std::wstring& to prevent unneeded calls to the std::basic_string copy constructor.

The CIniClass can be used in MFC. However when passing CString types to CIniFile functions, it is necessary to call the CString::GetSting() method or cast the CString to (LPCTSTR). The following examples will be demonstrated below:

  • Using Ansi C++\STL version of CIniFile under Linux
  • Using Ansi C++\STL version of CIniFile under Windows
  • Using Ansi C++\STL version in an MFC project

Sample Code Provided

To help with understanding how to use the code, many versions have been provided for download. The following downloads are provided:

  • Ansi C++\STL CIniFile (Code Blocks Project)
  • Ansi C++\STL CIniFile (Visual Studio 6.0 Project)
  • Ansi C++\STL CIniFile in Windows MFC (Visual Studio 6.0 Project)
  • Ansi C++\STL CIniFile (Visual Studio .NET 2005 Project)
  • Ansi C++\STL CIniFile in Windows MFC (Visual Studio .NET 2005 Project)

If you don't want entire projects, a source only version is available for download. Please see the section on "Using the source in a Windows MFC project". This explains what changes need to be made when using pre-compiled headers.

Efficiency

During testing on Ubuntu Linux 10.04 running on a Lenovo T60 w\2gb ram, CIniFile could generate an ini file holding 10000 sections with 100 keys per section and write it to disk in ~2 second. After increasing the keys per section to 1000, the file was generated in ~3 seconds. The file was 10,100,000 lines long and was roughly 227mb in size. Reading the data back from file into memory was in the same ball park. The same test was performed in an MFC project. The results were not quite as impressive taking around 3 seconds for the prior and 30 for the latter test. The sections and keys were written in the format below for both tests:

[Section0]
Key0=KeyValue
Key1=KeyValue
Key2=KeyValue
...
[Section1]
Key0=KeyValue
Key1=KeyValue
Key2=KeyValue
...

Preprocessor Definitions

The following values apply the Ansi C++\STL CIniFile library

Note: The CIniFile class currently supports (STL) std::wstring or std::string.

The typedef of CIniFile changes depending whether or not _UNICODE is defined. If _UNICODE is defined in your project, the CIniFile typedef is a CIniFileW if _UNICODE is not defined, then CIniFile typedef is a CIniFileA object. CIniFileW uses std::wstring for the support of wide characters. See the following preprocessor directives:

C++
// Additional defines
#ifdef _UNICODE
	#define INI_TOKEN_A INI_TOKEN_UNICODE
	#define INI_TOKEN_B INI_TOKEN_UNICODE
	#define INI_EMPTY INI_EMPTY_UNICODE
    typedef CIniFileW CIniFile;
    typedef CIniSectionW CIniSection;
    typedef CIniKeyW CIniKey;
    typedef PCINIW PCINI;
    typedef PCINIKEYW PCINIKEY;
    typedef PCINISECW PCINISEC;
    typedef KeyIndexW KeyIndex;
    typedef SecIndexW SecIndex;
#else
	#define INI_TOKEN_A INI_TOKEN_ANSI
	#define INI_TOKEN_B INI_TOKEN_ANSI
	#define INI_EMPTY INI_EMPTY_ANSI
    typedef CIniFileA CIniFile;
    typedef CIniSectionA CIniSection;
    typedef CIniKeyA CIniKey;
    typedef PCINIA PCINI;
    typedef PCINIKEYA PCINIKEY;
    typedef PCINISECA PCINISEC;
    typedef KeyIndexA KeyIndex;
    typedef SecIndexA SecIndex;
#endif

The CIniFile library under Visual Studio expects that _WIN32 is defined at compile time. Since windows doesn't support strcasecmp or wcscasecmp defining _WIN32 switches the functions to the respective windows versions. These functions are used by std::set as the custom comparator.

C++
// The following is defined in the CIniFileA class
struct ci_less_a
{
    bool operator() (const CIniSectionA* s1, const CIniSectionA* s2) const
    {
        #ifndef _WIN32
            return strcasecmp(s1->m_sSectionName.c_str(), s2->m_sSectionName.c_str()) < 0;
        #else
            return _stricmp(s1->m_sSectionName.c_str(), s2->m_sSectionName.c_str()) < 0;
        #endif
    }
};

// The following is defined in the CIniSectionA class
struct ci_less_a
{
    bool operator() (const CIniKeyA* s1, const CIniKeyA* s2) const
    {
        #ifndef _WIN32
            return strcasecmp(s1->m_sKeyName.c_str(), s2->m_sKeyName.c_str()) < 0;
        #else
            return _stricmp(s1->m_sKeyName.c_str(), s2->m_sKeyName.c_str()) < 0;
        #endif
    }
};

// The following is defined in the CIniFileW class
struct ci_less_w
{
    bool operator() (const CIniSectionW* s1, const CIniSectionW* s2) const
    {
        #ifndef _WIN32
            return strcasecmp(s1->m_sSectionName.c_str(), s2->m_sSectionName.c_str()) < 0;
        #else
            return _stricmp(s1->m_sSectionName.c_str(), s2->m_sSectionName.c_str()) < 0;
        #endif
    }
};

// The following is defined in the CIniSectionW class
struct ci_less_w
{
    bool operator() (const CIniKeyW* s1, const CIniKeyW* s2) const
    {
        #ifndef _WIN32
            return strcasecmp(s1->m_sKeyName.c_str(), s2->m_sKeyName.c_str()) < 0;
        #else
            return _stricmp(s1->m_sKeyName.c_str(), s2->m_sKeyName.c_str()) < 0;
        #endif
    }
};

Defines of interest:

  • _TRACE_CINIFILE - If defined, enables call tracing to standard output
  • _UNICODE - If defined, the CIniFile will be defined as CIniFileW instead of CIniFileA
  • _FORCE_UNIX_LINEFEED - If defined, when _WIN32 is defined (WINDOWS), the default linefeed CRLF is overridden to CR
  • _FORCE_WINDOWS_LINEFEED - If defined, when _WIN32 is not defined (*NIX) the default linefeed CR is overridden to CRLF

Parsing and Behavior

Currently the CIniFile is designed to read most ini files. Ini Sections should start with "[" and end with a "]". Whitespace between "[" and "]" will be trimmed. For example, the section defined below would be interpreted as "SECTION" not " SECTION ".

[          SECTION            ]
...
...
...

Ini Key\Value pairs should have a key value separated by an "=" to the right of the key value. Key values are also trimmed to remove whitespace. For example, the key defined below would be interpreted as "MyKeyValue=SomeData". The resulting key would be "MyKeyValue" and value would be "SomeData".

[          SECTION            ]
         MyKeyValue        =SomeData
...
...

Ini key values however are not trimmed and whitespace is preserved. For example, the key defined below would be interpreted as "MyIniKey= SomeDataWithSpaces ". The resulting key would be "MyKeyValue" and value would be " SomeDataWithSpaces ".

[          SECTION            ]
         MyKeyValue        =        SomeDataWithSpaces        
...
...

Functions and Returns

The following examples will show std::string for function arguments. Using CIniFileA or CIniFileW will result in the usage of std::string or std::wstring respectively. The typedef of CIniFile is based on the preprocessor directive _UNICODE. The constructors and destructors of the CIniSection and CIniKey object are private. The CIniFile class is responsible for creating CIniSection objects. The CIniSection object is responsible for creating CIniKey objects. This encapsulation prevents someone from deleting internally managed pointers. Each child class has a pointer to the parent object. CIniKey has a pointer to its parent CIniSection. CIniSection has a pointer back to the CIniObject it is associated with.

C++
// Methods of CIniFile

// Used to save the data back to the file or your choice. Returns true if saved
bool CIniFile::Save( const std::string& fileName );

// Used to save the ini data to an output stream
bool CIniFile::Save( std::ostream& output );

// Loads the data in the ini file into the IniFile object, 
// Returns true if loaded, false if failed to open file
// If bMerge is passed true, ini data will be merged into the existing ini object. 
// Merger overwrite similar section key values
bool CIniFile::Load( const std::string& fileName , bool bMerge = false );

// Loads data from an input stream which contains ini data. 
// If bMerge is passed true, ini data will be merged into the
// existing ini object. Merger overwrite similar section key values
void Load( std::istream& input , bool bMerge = false );

// Returns a const ref to the internal section index 
// ( can be used to get number of sections or enumerate them )
// See section on "Using CIniFile::GetSections and CIniSection::GetKeys"
const SecIndex& GetSections() const;

// Methods of CIniSection

// Adds a key to the CIniSection object, 
// returns a CIniKey pointer to the new or existing object
CIniKey* AddKey( std::string sKeyName );

// Removes a single key by pointer
void RemoveKey( CIniKey* pKey );

// Removes a single key by string
void RemoveKey( std::string sKey );

// Removes all the keys in the section
void RemoveAllKeys( );

// Returns a CIniKey pointer to the key by name, NULL if it was not found
CIniKey* GetKey( std::string sKeyName ) const;

// Returns a const ref to the internal key index 
// ( can be used to get number of keys or enumerate them  )
// See section on "Using CIniFile::GetSections and CIniSection::GetKeys"
const KeyIndex& GetKeys() const;

// Returns a KeyValue at a certain section
std::string GetKeyValue( std::string sKey ) const;

// Sets a KeyValuePair at a certain section
void SetKeyValue( std::string sKey, std::string sValue );

// Sets the section name, returns true on success, fails if the section
// name sSectionName already exists
bool SetSectionName( std::string sSectionName );

// Returns the section name [sectionname]
std::string GetSectionName() const;

// Methods of CIniKey

// Sets the string "value" of key=value
void SetValue( std::string sValue );

// Returns the string "value" of the key=value
std::string GetValue() const;

// Sets the key name, returns true on success, 
// fails if the key name sKeyName already exists in the parent section
bool SetKeyName( std::string sKeyName );

// Returns the string "key" of key=value
std::string GetKeyName() const;

// Operator overloads for ostream
std::ostream& operator<<(std::ostream& output, CIniFile& obj);

// Operator overloads for istream
std::istream& operator>>(std::istream& input, CIniFile& obj);

// Used to manipulate CIniFile input behavior. Allows >> to merge rather than overwrite.
std::istream& operator>>(std::istream& input, CIniMerge merger)

Using CIniFile::GetSections and CIniSection::GetKeys

The following code will demonstrate how to use the GetSection and GetKeys functions.

C++
// The code will print the [SECTIONS] and Key=Value for the ini file

CIniFile ini;

ini.Load("/tmp/inifile.ini");

// ini.GetSections().size() will return the number of ini sections in the file.

// Print all sections and keys using the following code
for( SecIndex::const_iterator itr = ini.GetSections().begin() ; 
	itr != ini.GetSections().end() ; ++itr )
{
  // (*itr)->GetSectionName() returns the name of the section
  // (*itr)->GetKeys().size() will return the number of keys associated with this section

  std::cout << "[" << (*itr)->GetSectionName() << "]" << std::endl;
  for( KeyIndex::const_iterator kitr = (*itr)->GetKeys().begin() ; 
	kitr != (*itr)->GetKeys().end() ; kitr++ )
  {
      // (*kitr)->->GetKeyName() returns is the name of the key
      // (*kitr)->->GetValue() returns is the value of the key

      std::cout << (*kitr)->->GetKeyName() << "=" << (*kitr)->->GetValue() << std::endl;
  }
}

Using CIniMerge Manipulator and C++ Streams

The following code will demonstrate how to use the CIniMerge with the >> operator.

C++
stringstream ss1;

ss1 << "[SOMESECTION]" << CIniFile::LF;
ss1 << "key1=value" << CIniFile::LF;
ss1 << "key2=value" << CIniFile::LF;
ss1 << "key3=value" << CIniFile::LF;

stringstream ss2;

ss2 << "[SOMESECTION2]" << CIniFile::LF;
ss2 << "key1=value" << CIniFile::LF;
ss2 << "key2=value" << CIniFile::LF;
ss2 << "key3=value" << CIniFile::LF;

CIniFile ini;

// Load ini using Load and stream
ini.Load( ss1 );

// Append to ini using Load and stream
// The resulting ini will be both sections
ini.Load( ss2 , true );

// Load ini using >>
// Note: The full contents of the existing ini file object 
// are dumped and the ss1 data is imported.
ss1 >> ini;

// Append to ini using >>
// The resulting ini will be both sections
ss2 >> CIniMerge(ini);

// Saving ini to stream using Save()
// The following will print the entire ini to standard out.
ini.Save( std::cout );

// Saving ini to stream using <<
// The following will print the entire ini to standard out.
std::cout << ini

Linux - Sample of Usage

C++
#include "inifile.h"
#include <iostream>

int main()
{
    // Note if _UNICODE would be defined CIniFile would require wide strings.
    // It is 100% valid to also use either CIniFileW or CIniFileA explicitly
    CIniFile ini;

    // Loading an existing INI file
    ini.Load("/tmp/test.ini");

    // Adding Sections
    ini.AddSection("Test1");

    // Adding sections and keys
    ini.AddSection("Test2")->AddKey("Test2Key");

    // Adding sections, keys, and values
    ini.AddSection("Test3")->AddKey("Test3Key")->SetValue("Test3KeyValue");

    // Getting an existing section of the ini
    CIniSection* pSection = ini.GetSection("Test3");

    if( pSection )
    {
            // Do stuff with that section
            pSection->SetSectionName("Test3NewSectionName");

            // Getting an existing key of the ini section
            CIniKey* pKey = pSection->GetKey("Test3Key");
            if( pKey )
            {
                    pKey->SetKeyName("Test3KeyNewName");
            }
    }

    // Getting key value using the ini function v.s getting the objects
    std::cout << "KeyValue: " << ini.GetKeyValue
	( "Test3NeWSECtionName" , "Test3KeyNewName" ) << std::endl;

    ini.Save("/tmp/testout.ini");

    return 0;
}

Windows (NON-MFC VS6 or VS 2005.NET) - Sample of Usage

C++
#include "inifile.h"
#include "tchar.h"
#include <iostream>

int _tmain()
{
    // Note if _UNICODE would be defined CIniFile would require 
    // wide strings (Note the _T(x) macro).
    // It is 100% valid to also use either CIniFileW or CIniFileA explicitly
    CIniFile ini;

    ini.Load(_T("C:\\temp\\testout.ini"));

    // Adding Sections
    ini.AddSection(_T("Test1"));

    // Adding sections and keys
    ini.AddSection(_T("Test2"))->AddKey(_T("Test2Key"));

    // Adding sections, keys, and values
    ini.AddSection(_T("Test3"))->AddKey(_T("Test3Key"))->SetValue(_T("Test3KeyValue"));

    // Getting an existing section of the ini
    CIniSection* pSection = ini.GetSection(_T("Test3"));

    if( pSection )
    {
            // Do stuff with that section
            pSection->SetSectionName(_T("Test3NewSectionName"));

            // Getting an existing key of the ini section
            CIniKey* pKey = pSection->GetKey(_T("Test3Key"));
            if( pKey )
            {
                    pKey->SetKeyName(_T("Test3KeyNewName"));
            }
    }

    // Getting key value using the ini function v.s getting the objects
    std::cout << _T("KeyValue: ") << ini.GetKeyValue
	( _T("Test3NeWSECtionName") , _T("Test3KeyNewName") ) << std::endl;

    ini.Save(_T("C:\\temp\\testout.ini"));

    return 0;
}

Windows (MFC) Visual Studio .NET 2005 - Sample of Usage

C++
#include "stdafx.h"
#include "inifile_mfc_vs.h"
#include "inifile.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// The one and only application object

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int nRetCode = 0;

	// initialize MFC and print and error on failure
	if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
	{
		// TODO: change error code to suit your needs
		_tprintf(_T("Fatal Error: MFC initialization failed\n"));
		nRetCode = 1;
	}
	else
	{
	// Using CString
	CString fileToSave = _T("C:\\temp\\testout.ini");

	// Note if _UNICODE would be defined CIniFile would require 
         // wide strings (Note the _T(x) macro).
    	// It is 100% valid to also use either CIniFileW or CIniFileA explicitly
	// Works Unicode or Multibyte
	CIniFile ini;

	// Load an existing INI
	ini.Load(_T("C:\\temp\\testout.ini"));

	// Adding Sections
	ini.AddSection(_T("Test1"));

	// Adding sections and keys
	ini.AddSection(_T("Test2"))->AddKey(_T("Test2Key"));

	// Adding sections, keys, and values
	ini.AddSection(_T("Test3"))->AddKey(_T("Test3Key"))->
				SetValue(_T("Test3KeyValue"));

	// Getting an existing section of the ini
	CIniSection* pSection = ini.GetSection(_T("Test3"));

    	if( pSection )
	    {
	            // Do stuff with that section
	            pSection->SetSectionName(_T("Test3NewSectionName"));

	            // Getting an existing key of the ini section
	            CIniKey* pKey = pSection->GetKey(_T("Test3Key"));
	            if( pKey )
	            {
	                    pKey->SetKeyName(_T("Test3KeyNewName"));
	            }
	    }

	// Getting key value using the ini function v.s getting the objects
	_tprintf(_T("KeyValue: %s\n"), 
		ini.GetKeyValue( _T("Test3NeWSECtionName") , _T("Test3KeyNewName") ) );

	// Note you must call GetString() to make the conversion
	ini.Save(fileToSave.GetString());

	}

	return nRetCode;
}

Windows (MFC) Visual Studio 6.0 - Sample of Usage

C++
#include "stdafx.h"
#include "inifile_mb_wide_mfc_vc6.h"
#include "inifile.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// The one and only application object

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	int nRetCode = 0;

	// initialize MFC and print and error on failure
	if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
	{
		// TODO: change error code to suit your needs
		cerr << _T("Fatal Error: MFC initialization failed") << endl;
		nRetCode = 1;
	}
	else
	{
	// Using CString
	CString fileToSave = "C:\\temp\\testout.ini";

	CIniFile ini;

	// Adding Sections
	ini.AddSection("Test1");

       	// Adding sections and keys
        ini.AddSection("Test2")->AddKey("Test2Key");

    	// Adding sections, keys, and values
	ini.AddSection("Test3")->AddKey("Test3Key")->SetValue("Test3KeyValue");

	// Getting an existing section of the ini
	CIniSection* pSection = ini.GetSection("Test3");

	if( pSection )
	{
        	// Do stuff with that section
        	pSection->SetSectionName("Test3NewSectionName");

        	// Getting an existing key of the ini section
        	CIniKey* pKey = pSection->GetKey("Test3Key");
        	if( pKey )
        	{
         		pKey->SetKeyName("Test3KeyNewName");
            	}
    	}

	// Getting key value using the ini function v.s getting the objects
	std::cout << "KeyValue: " << ini.GetKeyValue
		( "Test3NeWSECtionName" , "Test3KeyNewName" ) << std::endl;

	// Note to use CString in VC6 you must cast to LPCTSTR 
         // since GetString() is not available
	ini.Save((LPCTSTR)fileToSave);
	}

	return nRetCode;
}

Using the Source in a Windows MFC Project

To use the Ansi C++\STL version of the code in an MFC project using pre compiled headers, the precompiled header file must be added as the first line in the cinifile.cpp. See the example below:

C++
--------------- cinifile.cpp ---------------
#include "stdafx.h"
#include "inifile.h"
...
--------------- end of file ----------------

Future Enhancements

Perhaps multi key value support as mentioned in the comments below by another CodeProject user.

History

  • 12\01\2005 - Initial MFC release
  • 01\12\2006 - Ported to Ansi C++ Non-MFC
  • 06\16\2009 - Added support for different linefeed types, resolved issues around reading different types of linefeeds
  • 06\17\2009 - Added support for wide characters
  • 06\21\2009 - Re-written to use std::map
  • 07\02\2009 - Removed MFC version since Ansi version works in MFC ( Examples provided for download)
  • 07\03\2009 - Added support for VS6
  • 07\03\2009 - Fixed issue with SecMapA \ SecMapW. Were not named specific to the encoding may have caused issues
  • 07\03\2009 - Fixed GetKeys and GetSections functions to return const ref v.s. copy of data
  • 07\14\2009 - Fixed Load() whitespace preservation on key value
  • 07\26\2009 - Fixed incorrect define MSC_VER should have been _MSC_VER
  • 09\21\2009 - Fixed removing all the sections and keys, replaced empty() with clear()
  • 09\22\2009 - Added overloaded Load() and Save() to read\write streams
  • 09\23\2009 - Added operators for << and >> to be used with streams
  • 09\24\2009 - Added merge option to Load()
  • 09\25\2009 - Added CIniMerge for use with << and >>
  • 09\27\2009 - Moved CIniMerge into CIniFile, fixed issue with VC6 CIniFile::CR
  • 12\28\2010 - Reduced key storage redundancy by using std::set instead of std::map
  • 12\29\2010 - Reduced number of pass by value methods to reduce deep copy std::string to const std::string&
  • 05\07\2011 - Fixed MSC_VER to _MSC_VER
  • 05\07\2011 - Fixed OTHER file parse detection issue
  • 05\06\2021 - Moving to MIT License after many have reached out to me

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here