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 string
s 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:
#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.
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
}
};
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
}
};
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
}
};
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.
bool CIniFile::Save( const std::string& fileName );
bool CIniFile::Save( std::ostream& output );
bool CIniFile::Load( const std::string& fileName , bool bMerge = false );
void Load( std::istream& input , bool bMerge = false );
const SecIndex& GetSections() const;
CIniKey* AddKey( std::string sKeyName );
void RemoveKey( CIniKey* pKey );
void RemoveKey( std::string sKey );
void RemoveAllKeys( );
CIniKey* GetKey( std::string sKeyName ) const;
const KeyIndex& GetKeys() const;
std::string GetKeyValue( std::string sKey ) const;
void SetKeyValue( std::string sKey, std::string sValue );
bool SetSectionName( std::string sSectionName );
std::string GetSectionName() const;
void SetValue( std::string sValue );
std::string GetValue() const;
bool SetKeyName( std::string sKeyName );
std::string GetKeyName() const;
std::ostream& operator<<(std::ostream& output, CIniFile& obj);
std::istream& operator>>(std::istream& input, CIniFile& obj);
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.
CIniFile ini;
ini.Load("/tmp/inifile.ini");
for( SecIndex::const_iterator itr = ini.GetSections().begin() ;
itr != ini.GetSections().end() ; ++itr )
{
std::cout << "[" << (*itr)->GetSectionName() << "]" << std::endl;
for( KeyIndex::const_iterator kitr = (*itr)->GetKeys().begin() ;
kitr != (*itr)->GetKeys().end() ; kitr++ )
{
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.
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;
ini.Load( ss1 );
ini.Load( ss2 , true );
ss1 >> ini;
ss2 >> CIniMerge(ini);
ini.Save( std::cout );
std::cout << ini
Linux - Sample of Usage
#include "inifile.h"
#include <iostream>
int main()
{
CIniFile ini;
ini.Load("/tmp/test.ini");
ini.AddSection("Test1");
ini.AddSection("Test2")->AddKey("Test2Key");
ini.AddSection("Test3")->AddKey("Test3Key")->SetValue("Test3KeyValue");
CIniSection* pSection = ini.GetSection("Test3");
if( pSection )
{
pSection->SetSectionName("Test3NewSectionName");
CIniKey* pKey = pSection->GetKey("Test3Key");
if( pKey )
{
pKey->SetKeyName("Test3KeyNewName");
}
}
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
#include "inifile.h"
#include "tchar.h"
#include <iostream>
int _tmain()
{
CIniFile ini;
ini.Load(_T("C:\\temp\\testout.ini"));
ini.AddSection(_T("Test1"));
ini.AddSection(_T("Test2"))->AddKey(_T("Test2Key"));
ini.AddSection(_T("Test3"))->AddKey(_T("Test3Key"))->SetValue(_T("Test3KeyValue"));
CIniSection* pSection = ini.GetSection(_T("Test3"));
if( pSection )
{
pSection->SetSectionName(_T("Test3NewSectionName"));
CIniKey* pKey = pSection->GetKey(_T("Test3Key"));
if( pKey )
{
pKey->SetKeyName(_T("Test3KeyNewName"));
}
}
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
#include "stdafx.h"
#include "inifile_mfc_vs.h"
#include "inifile.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
_tprintf(_T("Fatal Error: MFC initialization failed\n"));
nRetCode = 1;
}
else
{
CString fileToSave = _T("C:\\temp\\testout.ini");
CIniFile ini;
ini.Load(_T("C:\\temp\\testout.ini"));
ini.AddSection(_T("Test1"));
ini.AddSection(_T("Test2"))->AddKey(_T("Test2Key"));
ini.AddSection(_T("Test3"))->AddKey(_T("Test3Key"))->
SetValue(_T("Test3KeyValue"));
CIniSection* pSection = ini.GetSection(_T("Test3"));
if( pSection )
{
pSection->SetSectionName(_T("Test3NewSectionName"));
CIniKey* pKey = pSection->GetKey(_T("Test3Key"));
if( pKey )
{
pKey->SetKeyName(_T("Test3KeyNewName"));
}
}
_tprintf(_T("KeyValue: %s\n"),
ini.GetKeyValue( _T("Test3NeWSECtionName") , _T("Test3KeyNewName") ) );
ini.Save(fileToSave.GetString());
}
return nRetCode;
}
Windows (MFC) Visual Studio 6.0 - Sample of Usage
#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
CWinApp theApp;
using namespace std;
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
{
cerr << _T("Fatal Error: MFC initialization failed") << endl;
nRetCode = 1;
}
else
{
CString fileToSave = "C:\\temp\\testout.ini";
CIniFile ini;
ini.AddSection("Test1");
ini.AddSection("Test2")->AddKey("Test2Key");
ini.AddSection("Test3")->AddKey("Test3Key")->SetValue("Test3KeyValue");
CIniSection* pSection = ini.GetSection("Test3");
if( pSection )
{
pSection->SetSectionName("Test3NewSectionName");
CIniKey* pKey = pSection->GetKey("Test3Key");
if( pKey )
{
pKey->SetKeyName("Test3KeyNewName");
}
}
std::cout << "KeyValue: " << ini.GetKeyValue
( "Test3NeWSECtionName" , "Test3KeyNewName" ) << std::endl;
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:
--------------- 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 stream
s - 09\23\2009 - Added operators for
<<
and >>
to be used with stream
s - 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