Skip navigation links

Source code for C_INIFile

Download

The source files for this module are listed below.  You can also download the module C_INIFile as a zip archive; this archive contains all the source files and documentation.

Description

CINIFile and helper classes allow you to use INI files without using the GetProvateProfileString, WriteProvateProfileString and related API functions, and instead read and write the whole INI file in one go.  This results in much faster read and write operations when you use multiple individual settings, especially when virus scanners inspect each and every INI file read and write.

Information

This source code also uses our CHandle class.  You can download this module at our website.

When you use the GetProvateProfileString and WriteProvateProfileString API functions, you will cause a lot of disk reads and writes because the actual INI file has to be read (and written) for each separate setting.  To improve disk access, it is a better idea to buffer all reads and writes and work with the in-memory buffer instead.  You can achieve this to some degree using the GetPrivateProfileSection and WritePrivateProfileSection API functions, but these functions require you to use your own INI section parsing routines.  The CINIFile however reads and writes an INI file in one go.  It allows you to perform all INI setting actions from an in-memory buffer.

The CINIFile class allows you to query for and access individual sections of the INI file.  If you want to create sections (or make sure a section exists), specify bCreateIfMissing as true on the call to GetSection.  If the section wasn't present before, an empty section will be created first and it will be returned.

CINISection in turn allows you to access individual settings in that section.  Here you can also specify to create the setting if it is missing, allowing you to create new settings on the fly.  CINISection also allows you to retrieve the entire section in one string using the Serialize method; the result is a string you can use to mimic the Get/WritePrivateProfileSection API functions.

CINISetting finally allows you to retrieve the actual settings in the INI file.  It has different get/set methods to read/write the setting in different formats.  The Get methods also allow you to specify a default to use if the settings was missing, just like the API functions do.

Notes / todo's

  • The original lay-out and section/settings order of the INI file is not purposely retained when it is written out again, and might be totally different compared to the original order.
  • Comments in the read INI file are not accessible nor retained when you write the INI file again.

Files

Each file belonging to this source code module is listed below.

INIFile.h

/*******************************************************************************

  Version: 2
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  http://www.twologs.com/en/resources/sourcecode.asp

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/
#ifndef INCLUDE_TWOLOGS_COMMON_INIFILE_H
#define INCLUDE_TWOLOGS_COMMON_INIFILE_H

#include <windows.h>
#include <string>
#include <map>

// Single setting
class CINISetting {
public:
  // Con- & destructor
  CINISetting(LPCSTR sName);
  CINISetting(const std::string& sName);
  virtual ~CINISetting();

  // The name
  std::string GetName() const;

  // Gets the value
  std::string GetText(LPCSTR sDefault = "") const;
  std::string GetText(const std::string& sDefault) const;
  bool GetBool(bool bDefault = false) const;
  unsigned long GetUnsigned(unsigned long nDefault = 0) const;
  long GetSigned(long nDefault = 0) const;
  unsigned long long GetUnsigned64Bit(unsigned long long nDefault = 0) const;
  long long GetSigned64Bit(long long nDefault = 0) const;
  double GetDouble(double nDefault = 0) const;

  // Sets the value
  void SetText(LPCSTR sValue);
  void SetText(const std::string& sValue);
  void SetUnsigned(unsigned long nValue);
  void SetSigned(long nValue);
  void SetUnsigned64Bit(unsigned long long nValue);
  void SetSigned64Bit(long long nValue);
  void SetBool(bool bValue);
  void SetDouble(double nValue);

private:
  // The name
  std::string m_sName;

  // The value
  std::string m_sValue;
};

// Section serialization styles
enum EINISectionSerStyle {
  g_ceINISectSerStyleAPI,            // Windows API style (NULL char separators)
  g_ceINISectSerStyleText,           // Textual style (newline separator)
  g_ceINISectSerStyleTextWithHeader, // Textual style with section header in brackets
};

// Section of settings
class CINISection {
public:
  // Con- & destructor
  CINISection(LPCSTR sName);
  virtual ~CINISection();

  // Gets our name
  std::string GetName() const;

  // Whether the given setting is present
  bool HasSetting(LPCSTR sName) const;
  bool HasSetting(const std::string& sName) const;

  // Gets the given setting
  CINISetting* GetSetting(LPCSTR sName, bool bCreateIfMissing = true);
  CINISetting* GetSetting(const std::string& sName, bool bCreateIfMissing = true);

  // Clears the entire section
  void Clear();

  // Serializes the section
  std::string Serialize(EINISectionSerStyle eStyle);

private:
  // Our name
  std::string m_sName;

  // All our settings by name
  typedef std::map<std::string, CINISetting*> CSettingsByName;
  CSettingsByName m_apoSettingsByName;

  // Searches for the given setting
  CSettingsByName::iterator SearchSetting(const std::string& sSearchableName);
  CSettingsByName::const_iterator SearchSetting(const std::string& sSearchableName) const;
};

// Entire INI file
class CINIFile {
public:
  // Con- & destructor
  CINIFile();
  virtual ~CINIFile();

  // Reads from the given INI file
  bool Read(LPCSTR sFilePath);

  // Writes to the last used or the given INI file
  bool Write(LPCSTR sFilePath = NULL);

  // The file currently in use
  std::string FilePath() const;

  // Whether the given section is present
  bool HasSection(LPCSTR sName) const;
  bool HasSection(const std::string& sName) const;

  // Gets the given section
  CINISection* GetSection(LPCSTR sName, bool bCreateIfMissing = true);
  CINISection* GetSection(const std::string& sName, bool bCreateIfMissing = true);

private:
  // The file/path of the INI file to use
  std::string m_sFilePath;

  // All our sections
  typedef std::map<std::string, CINISection*> CSectionsByName;
  CSectionsByName m_apoSectionsByName;

  // Searches for the given section
  CSectionsByName::iterator SearchSection(const std::string& sSearchableName);
  CSectionsByName::const_iterator SearchSection(const std::string& sSearchableName) const;
};

#endif // INCLUDE_TWOLOGS_COMMON_INIFILE_H

INIFile.cpp

#include "INIFile.h"
#include "..\C_Handle\Handle.h"
#include <list>
#include <cstdio>

// Trims the given string
void Trim(std::string& sText) {
  DWORD nStartPos = sText.find_first_not_of(" \t");
  DWORD nEndPos = sText.find_last_not_of(" \t");
  if (nStartPos == sText.npos) {
    sText = "";
  } else {
    sText = sText.substr(nStartPos, nEndPos - nStartPos + 1);
  }
}

// Splits the given string into separate lines
typedef std::list<std::string> CStringList;
CStringList SplitString(const std::string& sString) {
  // Start at the start of the string
  CStringList asLines;
  DWORD nStartLine = 0;
  bool bLastLine;
  do {
    // Get the end of the next line
    DWORD nEndLine = sString.find_first_of("\r\n", nStartLine);
    bLastLine = nEndLine == sString.npos;
    if (bLastLine) {
      // There isn't -> take all chars to the end of the string
      nEndLine = sString.size() + 1;
    }

    // Extract the next line
    std::string sNextLine = sString.substr(nStartLine, nEndLine - nStartLine);

    // Look it it contains any data
    Trim(sNextLine);
    if (sNextLine.size() > 0) {
      // Yes -> add it
      asLines.push_back(sNextLine);
    }

    // And find the start of the next line
    nStartLine = sString.find_first_not_of("\r\n", nEndLine);
    if (nStartLine == sString.npos) {
      // There is none -> we're done
      bLastLine = true;
    }
  } while(!bLastLine);

  // And return the found lines
  return asLines;
}

// Transforms the given name to a searchable form
std::string MakeSearchable(LPCSTR sName) {
  std::string sSearchable = sName;
  strlwr(const_cast<char*>(sSearchable.c_str()));
  return sSearchable;
}




// Con- & destructor
CINISetting::CINISetting(LPCSTR sName):
 m_sName(sName),
 m_sValue("") {
}
CINISetting::CINISetting(const std::string& sName):
 m_sName(sName),
 m_sValue("") {
}
CINISetting::~CINISetting() {
}

// The name
std::string CINISetting::GetName() const {
  return m_sName;
}

// Gets the value
std::string CINISetting::GetText(LPCSTR sDefault) const {
  std::string sResult = m_sValue;
  if (m_sValue.size() == 0) {
    sResult = sDefault;
  }
  return sResult;
}
std::string CINISetting::GetText(const std::string& sDefault) const {
  return GetText(sDefault.c_str());
}
bool CINISetting::GetBool(bool bDefault) const {
  bool bResult = bDefault;
  if (m_sValue.size() > 0) {
    switch (m_sValue.at(0)) {
      case 't':
      case 'T':
      case 'y':
      case 'Y':
      case '1':
        bResult = true;
        break;
      case 'f':
      case 'F':
      case 'n':
      case 'N':
      case '0':
        bResult = false;
        break;
    }
  }
  return bResult;
}
unsigned long CINISetting::GetUnsigned(unsigned long nDefault) const {
  unsigned long nResult = nDefault;
  if (m_sValue.size() > 0)
    nResult = strtoul(m_sValue.c_str(), NULL, 10);
  return nResult;
}
long CINISetting::GetSigned(long nDefault) const {
  long nResult = nDefault;
  if (m_sValue.size() > 0)
    nResult = atol(m_sValue.c_str());
  return nResult;
}
unsigned long long CINISetting::GetUnsigned64Bit(unsigned long long nDefault) const {
  unsigned long long nResult = nDefault;
  if (m_sValue.size() > 0)
    nResult = strtoull(m_sValue.c_str(), NULL, 10);
  return nResult;
}
long long CINISetting::GetSigned64Bit(long long nDefault) const {
  long long nResult = nDefault;
  if (m_sValue.size() > 0)
    nResult = strtoll(m_sValue.c_str(), NULL, 10);
  return nResult;
}
double CINISetting::GetDouble(double nDefault) const {
  double nResult = nDefault;
  if (m_sValue.size() > 0)
    nResult = atof(m_sValue.c_str());
  return nResult;
}

// Sets the value
void CINISetting::SetText(LPCSTR sValue) {
  m_sValue = sValue;
}
void CINISetting::SetText(const std::string& sValue) {
  m_sValue = sValue;
}
void CINISetting::SetUnsigned(unsigned long nValue) {
  char sValue[30];
  _ultoa(nValue, sValue, 10);
  m_sValue = sValue;
}
void CINISetting::SetSigned(long nValue) {
  char sValue[30];
  ltoa(nValue, sValue, 10);
  m_sValue = sValue;
}
void CINISetting::SetUnsigned64Bit(unsigned long long nValue) {
  char sValue[60];
  ulltoa(nValue, sValue, 10);
  m_sValue = sValue;
}
void CINISetting::SetSigned64Bit(long long nValue) {
  char sValue[60];
  lltoa(nValue, sValue, 10);
  m_sValue = sValue;
}
void CINISetting::SetBool(bool bValue) {
  m_sValue = bValue? "y": "n";
}
void CINISetting::SetDouble(double nValue) {
  char sValue[128];
  sprintf(sValue, "%.8f", nValue);
  m_sValue = sValue;
}





// Con- & destructor
CINISection::CINISection(LPCSTR sName):
 m_sName(sName) {
}
CINISection::~CINISection() {
  Clear();
}

// Gets our name
std::string CINISection::GetName() const {
  return m_sName;
}

// Whether the given setting is present
bool CINISection::HasSetting(LPCSTR sName) const {
  std::string sSearchableName = MakeSearchable(sName);
  CSettingsByName::const_iterator ppoFoundSetting = SearchSetting(sSearchableName);
  return ppoFoundSetting != m_apoSettingsByName.end();
}
bool CINISection::HasSetting(const std::string& sName) const {
  return HasSetting(sName.c_str());
}

// Gets the given setting
CINISetting* CINISection::GetSetting(LPCSTR sName, bool bCreateIfMissing) {
  // Find the setting
  std::string sSearchableName = MakeSearchable(sName);
  CINISetting* poFoundSetting;
  CSettingsByName::const_iterator ppoFoundSetting = SearchSetting(sSearchableName);
  bool bSettingFound = ppoFoundSetting != m_apoSettingsByName.end();
  if (bSettingFound) {
    // Done -> return it
    poFoundSetting = ppoFoundSetting->second;
  } else {
    // Not found -> look if we may create it
    if (bCreateIfMissing) {
      // Yes -> do so
      poFoundSetting = new CINISetting(sName);
      m_apoSettingsByName.insert(CSettingsByName::value_type(sSearchableName, poFoundSetting));
    } else {
      // No -> no setting
      poFoundSetting = NULL;
    }
  }

  // And return the found setting
  return poFoundSetting;
}

// Gets the given setting
CINISetting* CINISection::GetSetting(const std::string& sName, bool bCreateIfMissing) {
  return GetSetting(sName.c_str(), bCreateIfMissing);
}

// Clears the entire section
void CINISection::Clear() {
  // Kill all our settings
  CSettingsByName::iterator ppoNextSetting = m_apoSettingsByName.begin();
  CSettingsByName::iterator ppoLastSetting = m_apoSettingsByName.end();
  for (; ppoNextSetting != ppoLastSetting; ++ppoNextSetting) {
    CINISetting* poNextSetting = ppoNextSetting->second;
    delete poNextSetting;
  }
  m_apoSettingsByName.clear();
}

// Serializes the section
std::string CINISection::Serialize(EINISectionSerStyle eStyle) {
  // Start with the section header, if needed
  std::string sSerialized;
  bool bSomeSerialized = false;
  if (eStyle == g_ceINISectSerStyleTextWithHeader) {
    sSerialized = "[";
    sSerialized += m_sName;
    sSerialized += "]";
    bSomeSerialized = true;
  }

  // Add all settings
  CSettingsByName::iterator ppoNextSetting = m_apoSettingsByName.begin();
  CSettingsByName::iterator ppoLastSetting = m_apoSettingsByName.end();
  for (; ppoNextSetting != ppoLastSetting; ++ppoNextSetting) {
    // Add this setting
    CINISetting* poNextSetting = ppoNextSetting->second;
    if (bSomeSerialized) {
      if (eStyle == g_ceINISectSerStyleAPI) {
        sSerialized.append(1, '\0');
      } else {
        sSerialized += "\r\n";
      }
    }
    sSerialized += poNextSetting->GetName();
    sSerialized += "=";
    sSerialized += poNextSetting->GetText();
    bSomeSerialized = true;
  }

  // Finish off the serialisation, if needed
  if (eStyle == g_ceINISectSerStyleAPI) {
    sSerialized.append(1, '\0');
  }

  // And return the serialized content
  return sSerialized;
}

// Searches for the given setting
CINISection::CSettingsByName::iterator CINISection::SearchSetting(const std::string& sSearchableName) {
  return m_apoSettingsByName.find(sSearchableName);
}
CINISection::CSettingsByName::const_iterator CINISection::SearchSetting(const std::string& sSearchableName) const {
  return m_apoSettingsByName.find(sSearchableName);
}




// Con- & destructor
CINIFile::CINIFile() {
}
CINIFile::~CINIFile() {
  // Kill all our sections
  CSectionsByName::iterator ppoNextSection = m_apoSectionsByName.begin();
  CSectionsByName::iterator ppoLastSection = m_apoSectionsByName.end();
  for (; ppoNextSection != ppoLastSection; ++ppoNextSection) {
    CINISection* poNextSection = ppoNextSection->second;
    delete poNextSection;
  }
}

// Reads from the given INI file
bool CINIFile::Read(LPCSTR sFilePath) {
  // Note which file we should use
  m_sFilePath = sFilePath;

  // Open the source INI file
  CFileHandle hFile = CreateFile(
    sFilePath,
    GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL
  );
  bool bSuccess = hFile.IsSet();
  if (bSuccess) {
    // Done -> look how big the INI is
    DWORD nNumBytesToRead = GetFileSize(hFile, NULL);
    bSuccess = nNumBytesToRead != INVALID_FILE_SIZE;
    if (bSuccess) {
      // Done -> read out the INI content
      std::string sSerialized;
      sSerialized.resize(nNumBytesToRead);
      DWORD nNumBytesRead;
      bSuccess = 0 != ReadFile(
        hFile,
        const_cast<char*>(sSerialized.c_str()),
        nNumBytesToRead,
        &nNumBytesRead,
        NULL
      );
      if (bSuccess) {
        // Done -> look if all of the content is read
        bSuccess = nNumBytesToRead == nNumBytesToRead;
        if (bSuccess) {
          // Yes -> process each line
          CINISection* poCurrentSection = NULL;
          CStringList asLines = SplitString(sSerialized);
          CStringList::iterator psNextLine = asLines.begin();
          CStringList::iterator psLastLine = asLines.end();
          for (; psNextLine != psLastLine; ++psNextLine) {
            // Look what we have here
            std::string sNextLine = *psNextLine;
            if (sNextLine.size() > 2 && *sNextLine.begin() == '[' && *sNextLine.rbegin() == ']') {
              // A section header -> extract the name of the section
              std::string sName = sNextLine.substr(1, sNextLine.size() - 2);
              Trim(sName);

              // Look if this section is already present
              std::string sSearchableName = MakeSearchable(sName.c_str());
              CSectionsByName::iterator ppoFoundSection = SearchSection(sSearchableName);
              if (ppoFoundSection != m_apoSectionsByName.end()) {
                // Yes -> just recycle it
                poCurrentSection = ppoFoundSection->second;
              } else {
                // No -> create it
                poCurrentSection = new CINISection(sName.c_str());
                m_apoSectionsByName.insert(CSectionsByName::value_type(sSearchableName, poCurrentSection));
              }
            } else if (sNextLine.size() > 0 && poCurrentSection != NULL) {
              // A line with something else on it in a section -> look if it is a setting
              DWORD nSettingSepPos = sNextLine.find('=');
              if (nSettingSepPos != sNextLine.npos && nSettingSepPos > 0) {
                // Yes -> extract it
                std::string sName = sNextLine.substr(0, nSettingSepPos);
                Trim(sName);
                std::string sValue = sNextLine.substr(nSettingSepPos + 1);
                Trim(sValue);

                // And set the setting
                poCurrentSection->GetSetting(sName, true)->SetText(sValue);
              }
            }
          }
        }
      }
    }
  }

  // And return if successfull
  return bSuccess;
}

// Writes to the given INI file
bool CINIFile::Write(LPCSTR sFilePath) {
  // Serialize all sections
  bool bFirstSectionProcessed = false;
  std::string sSerialized;
  CSectionsByName::iterator ppoNextSection = m_apoSectionsByName.begin();
  CSectionsByName::iterator ppoLastSection = m_apoSectionsByName.end();
  for (; ppoNextSection != ppoLastSection; ++ppoNextSection) {
    // Add this section
    CINISection* poNextSection = ppoNextSection->second;
    if (bFirstSectionProcessed) {
      sSerialized += "\r\n\r\n";
    }
    sSerialized += poNextSection->Serialize(g_ceINISectSerStyleTextWithHeader);
    bFirstSectionProcessed = true;
  }

  // Look which file to use
  bool bWriteToOtherFile = sFilePath != NULL;
  if (!bWriteToOtherFile) {
    // The previously used one -> do so
    sFilePath = m_sFilePath.c_str();
  }

  // Open the destination INI file
  CFileHandle hFile = CreateFile(
    sFilePath,
    GENERIC_WRITE,
    0,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL
  );
  bool bSuccess = hFile.IsSet();
  if (bSuccess) {
    // Done -> write out the INI content
    DWORD nNumBytesToWrite = sSerialized.size();
    DWORD nNumBytesWritten;
    bSuccess = 0 != WriteFile(
      hFile,
      sSerialized.c_str(),
      nNumBytesToWrite,
      &nNumBytesWritten,
      NULL
    );
    if (bSuccess) {
      // Done -> look if all of the content is written
      bSuccess = nNumBytesToWrite == nNumBytesWritten;
      if (bSuccess) {
        // Done -> remember which file we used, if needed
        if (bWriteToOtherFile) {
          m_sFilePath = sFilePath;
        }
      }
    }
  }

  // And return if successfull
  return bSuccess;
}

// The file currently in use
std::string CINIFile::FilePath() const {
  return m_sFilePath;
}

// Whether the given section is present
bool CINIFile::HasSection(LPCSTR sName) const {
  std::string sSearchableName = MakeSearchable(sName);
  CSectionsByName::const_iterator ppoFoundSection = SearchSection(sSearchableName);
  return ppoFoundSection != m_apoSectionsByName.end();
}
bool CINIFile::HasSection(const std::string& sName) const {
  return HasSection(sName.c_str());
}

// Gets the given section
CINISection* CINIFile::GetSection(LPCSTR sName, bool bCreateIfMissing) {
  // Find the section
  std::string sSearchableName = MakeSearchable(sName);
  CINISection* poFoundSection;
  CSectionsByName::const_iterator ppoFoundSection = SearchSection(sSearchableName);
  bool bSectionFound = ppoFoundSection != m_apoSectionsByName.end();
  if (bSectionFound) {
    // Done -> return it
    poFoundSection = ppoFoundSection->second;
  } else {
    // Not found -> look if we may create it
    if (bCreateIfMissing) {
      // Yes -> do so
      poFoundSection = new CINISection(sName);
      m_apoSectionsByName.insert(CSectionsByName::value_type(sSearchableName, poFoundSection));
    } else {
      // No -> no section
      poFoundSection = NULL;
    }
  }

  // And return the found section
  return poFoundSection;
}

// Gets the given section
CINISection* CINIFile::GetSection(const std::string& sName, bool bCreateIfMissing) {
  return GetSection(sName.c_str(), bCreateIfMissing);
}

// Searches for the given section
CINIFile::CSectionsByName::iterator CINIFile::SearchSection(const std::string& sSearchableName) {
  return m_apoSectionsByName.find(sSearchableName);
}
CINIFile::CSectionsByName::const_iterator CINIFile::SearchSection(const std::string& sSearchableName) const {
  return m_apoSectionsByName.find(sSearchableName);
}