Skip navigation links
IT services and product development
Menu
TwoLogs
IT services and product development

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.  When you have your INI content in a string buffer, then you can use CINIContent to handle the INI content the same way.

Information

This source code also uses our C_Handle and C_Unicode modules.  You can download these modules 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 copy the content of an INI file into a CINIContent object.  You can in turn use the CINIContent object 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.  You can also retrieve all sections seperately in turn by using the GetSection method.

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: 6
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  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 <vector>
#include <map>
#include "..\C_Unicode\Unicode.h"

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

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

  // Gets the value
  std::wstring GetText(LPCWSTR sDefault = L"") const;
  std::wstring GetText(const std::wstring& sDefault) const;
  bool GetBool(bool bDefault = false) const;
  unsigned long GetUnsigned(unsigned long nDefault = 0) const;
  template <typename EEnum>
  EEnum GetEnum(EEnum eDefault = EEnum(0)) {
    return (EEnum)GetUnsigned((unsigned long)eDefault);
  }
  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(LPCWSTR sValue);
  void SetText(const std::wstring& sValue);
  void SetUnsigned(unsigned long nValue);
  template <typename EEnum>
  void SetEnum(EEnum eValue) {
    SetUnsigned((unsigned long) eValue);
  }
  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::wstring m_sName;

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

// Section serialization styles
enum class EINISectionSerStyle {
  eAPI,            // Windows API style (NULL char separators)
  eText,           // Textual style (newline separator)
  eTextWithHeader  // Textual style with section header in brackets
};

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

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

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

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

  // Clears the entire section
  void Clear();

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

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

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

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

// INI content
class CINIContent {
public:
  // Con- & destructor
  CINIContent();
  virtual ~CINIContent();

  // Sets the INI content to the given string
  void SetTo(const wchar_t* sContent);
  void SetTo(const std::wstring& sContent);

  // Gets the complete content as a string
  std::wstring GetAll() const;

  // The number of sections defined
  size_t NumSections() const;

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

  // Gets the given section
  CINISection* GetSection(size_t nSectionNr);
  CINISection* GetSection(LPCWSTR sName, bool bCreateIfMissing = true);
  CINISection* GetSection(const std::wstring& sName, bool bCreateIfMissing = true);

private:
  // All our sections
  typedef std::vector<CINISection*> CSections;
  CSections m_apoSections;
  typedef std::map<std::wstring, CINISection*> CSectionsByName;
  CSectionsByName m_apoSectionsByName;

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

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

  // Reads from the given INI file
  // The format of the file is deducted from any found BOM, but you
  // can specify the format in case no BOM is found
  bool Read(LPCWSTR sFilePath, EUnicodeFormat eFormat = EUnicodeFormat::eASCII);

  // Writes to the last used or the given INI file in the specified format
  // Note that UTF8 INI files cannot be read by the rest of Windows
  bool Write(LPCWSTR sFilePath = nullptr);
  bool Write(CExternalUnicodeFormat oFormat, LPCWSTR sFilePath = nullptr);

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

  // The format of the file currently in use
  const CExternalUnicodeFormat& Format() const;

  // Gets the content
  CINIContent& Content();
  const CINIContent& Content() const;

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

  // The format of the INI file in use
  CExternalUnicodeFormat m_oFormat;

  // Our content
  CINIContent m_oContent;
};

#endif // INCLUDE_TWOLOGS_COMMON_INIFILE_H

INIFile.cpp

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

  Version: 6
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  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.

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

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

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

// Splits the given string into separate lines
typedef std::list<std::wstring> CStringList;
CStringList SplitString(const std::wstring& 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(L"\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::wstring 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(L"\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::wstring MakeSearchable(LPCWSTR sName) {
  std::wstring sSearchable = sName;
  wcslwr(const_cast<wchar_t*>(sSearchable.c_str()));
  return sSearchable;
}




// Con- & destructor
CINISetting::CINISetting(LPCWSTR sName):
 m_sName(sName),
 m_sValue(L"") {
}
CINISetting::CINISetting(const std::wstring& sName):
 m_sName(sName),
 m_sValue(L"") {
}
CINISetting::~CINISetting() {
}

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

// Gets the value
std::wstring CINISetting::GetText(LPCWSTR sDefault) const {
  std::wstring sResult = m_sValue;
  if (m_sValue.size() == 0) {
    sResult = sDefault;
  }
  return sResult;
}
std::wstring CINISetting::GetText(const std::wstring& 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 L't':
      case L'T':
      case L'y':
      case L'Y':
      case L'1':
        bResult = true;
        break;
      case L'f':
      case L'F':
      case L'n':
      case L'N':
      case L'0':
        bResult = false;
        break;
    }
  }
  return bResult;
}
unsigned long CINISetting::GetUnsigned(unsigned long nDefault) const {
  unsigned long nResult = nDefault;
  if (m_sValue.size() > 0)
    nResult = wcstoul(m_sValue.c_str(), nullptr, 10);
  return nResult;
}
long CINISetting::GetSigned(long nDefault) const {
  long nResult = nDefault;
  if (m_sValue.size() > 0)
    nResult = _wtol(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 = wcstoull(m_sValue.c_str(), nullptr, 10);
  return nResult;
}
long long CINISetting::GetSigned64Bit(long long nDefault) const {
  long long nResult = nDefault;
  if (m_sValue.size() > 0)
    nResult = wcstoll(m_sValue.c_str(), nullptr, 10);
  return nResult;
}
double CINISetting::GetDouble(double nDefault) const {
  double nResult = nDefault;
  if (m_sValue.size() > 0) {
    wchar_t* sEndPos;
    nResult = wcstod(m_sValue.c_str(), &sEndPos);
  }
  return nResult;
}

// Sets the value
void CINISetting::SetText(LPCWSTR sValue) {
  m_sValue = sValue;
}
void CINISetting::SetText(const std::wstring& sValue) {
  m_sValue = sValue;
}
void CINISetting::SetUnsigned(unsigned long nValue) {
  wchar_t sValue[30];
  _ultow(nValue, sValue, 10);
  m_sValue = sValue;
}
void CINISetting::SetSigned(long nValue) {
  wchar_t sValue[30];
  _ltow(nValue, sValue, 10);
  m_sValue = sValue;
}
void CINISetting::SetUnsigned64Bit(unsigned long long nValue) {
  wchar_t sValue[60];
  ulltow(nValue, sValue, 10);
  m_sValue = sValue;
}
void CINISetting::SetSigned64Bit(long long nValue) {
  wchar_t sValue[60];
  lltow(nValue, sValue, 10);
  m_sValue = sValue;
}
void CINISetting::SetBool(bool bValue) {
  m_sValue = bValue? L"y": L"n";
}
void CINISetting::SetDouble(double nValue) {
  wchar_t sValue[128];
  swprintf(sValue, L"%.8f", nValue);
  m_sValue = sValue;
}





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

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

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

// Gets the given setting
CINISetting* CINISection::GetSetting(LPCWSTR sName, bool bCreateIfMissing) {
  // Find the setting
  std::wstring sSearchableName = MakeSearchable(sName);
  CINISetting* poFoundSetting;
  auto 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[sSearchableName] = poFoundSetting;
    } else {
      // No -> no setting
      poFoundSetting = nullptr;
    }
  }

  // And return the found setting
  return poFoundSetting;
}

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

// Clears the entire section
void CINISection::Clear() {
  // Kill all our settings
  for (auto ppoNextSetting: m_apoSettingsByName) {
    delete ppoNextSetting.second;
  }
  m_apoSettingsByName.clear();
}

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

  // Add all settings
  for (auto ppoNextSetting: m_apoSettingsByName) {
    // Add this setting
    CINISetting* poNextSetting = ppoNextSetting.second;
    if (bSomeSerialized) {
      if (eStyle == EINISectionSerStyle::eAPI) {
        sSerialized.append(1, L'\0');
      } else {
        sSerialized += L"\r\n";
      }
    }
    sSerialized += poNextSetting->GetName();
    sSerialized += L"=";
    sSerialized += poNextSetting->GetText();
    bSomeSerialized = true;
  }

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

  // And return the serialized content
  return sSerialized;
}

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




// Con- & destructor
CINIContent::CINIContent() {
}
CINIContent::~CINIContent() {
  // Kill all our sections
  for (CINISection* poNextSection: m_apoSections) {
    delete poNextSection;
  }
}

// Sets the INI content to the given string
void CINIContent::SetTo(const std::wstring& sContent) {
  SetTo(sContent.c_str());
}
void CINIContent::SetTo(const wchar_t* sContent) {
  // Split the content into seperate lines
  CStringList asLines = SplitString(sContent);

  // And process each line
  CINISection* poCurrentSection = nullptr;
  for (const std::wstring& sNextLine: asLines) {
    // Look what we have here
    if (sNextLine.size() > 2 && *sNextLine.begin() == L'[' && *sNextLine.rbegin() == L']') {
      // A section header -> extract the name of the section
      std::wstring sName = sNextLine.substr(1, sNextLine.size() - 2);
      Trim(sName);

      // Look if this section is already present
      std::wstring sSearchableName = MakeSearchable(sName.c_str());
      auto 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[sSearchableName] = poCurrentSection;
        m_apoSections.push_back(poCurrentSection);
      }
    } else if (sNextLine.size() > 0 && poCurrentSection != nullptr) {
      // A line with something else on it in a section -> look if it is a setting
      DWORD nSettingSepPos = sNextLine.find(L'=');
      if (nSettingSepPos != sNextLine.npos && nSettingSepPos > 0) {
        // Yes -> extract it
        std::wstring sName = sNextLine.substr(0, nSettingSepPos);
        Trim(sName);
        std::wstring sValue = sNextLine.substr(nSettingSepPos + 1);
        Trim(sValue);

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

// Gets the complete content as a string
std::wstring CINIContent::GetAll() const {
  // Serialize all sections
  bool bFirstSectionProcessed = false;
  std::wstring sSerialized;
  for (CINISection* poNextSection: m_apoSections) {
    // Add this section
    if (bFirstSectionProcessed) {
      sSerialized += L"\r\n\r\n";
    }
    sSerialized += poNextSection->Serialize(EINISectionSerStyle::eTextWithHeader);
    bFirstSectionProcessed = true;
  }

  // And return the result
  return sSerialized;
}

// The number of sections defined
size_t CINIContent::NumSections() const {
  return m_apoSections.size();
}

// Whether the given section is present
bool CINIContent::HasSection(LPCWSTR sName) const {
  std::wstring sSearchableName = MakeSearchable(sName);
  auto ppoFoundSection = SearchSection(sSearchableName);
  return ppoFoundSection != m_apoSectionsByName.end();
}
bool CINIContent::HasSection(const std::wstring& sName) const {
  return HasSection(sName.c_str());
}

// Gets the given section
CINISection* CINIContent::GetSection(size_t nSectionNr) {
  CINISection* poFoundSection;
  if (nSectionNr < m_apoSections.size()) {
    poFoundSection = m_apoSections[nSectionNr];
  } else {
    poFoundSection = nullptr;
  }
  return poFoundSection;
}

// Gets the given section
CINISection* CINIContent::GetSection(LPCWSTR sName, bool bCreateIfMissing) {
  // Find the section
  std::wstring sSearchableName = MakeSearchable(sName);
  CINISection* poFoundSection;
  auto 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[sSearchableName] = poFoundSection;
      m_apoSections.push_back(poFoundSection);
    } else {
      // No -> no section
      poFoundSection = nullptr;
    }
  }

  // And return the found section
  return poFoundSection;
}

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

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





// Con- & destructor
CINIFile::CINIFile() {
}
CINIFile::~CINIFile() {
}

// Reads from the given INI file
// The format of the file is deducted from any found BOM, but you
// can specify the format in case no BOM is found
bool CINIFile::Read(LPCWSTR sFilePath, EUnicodeFormat eFormat) {
  // Note which file we should use
  m_sFilePath = sFilePath;

  // Open the source INI file
  CFileHandle hFile = CreateFile(
    sFilePath,
    GENERIC_READ,
    FILE_SHARE_READ,
    nullptr,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    NULL
  );
  bool bSuccess = hFile.IsSet();
  if (bSuccess) {
    // Done -> look how big the INI is
    DWORD nNumBytesToRead = GetFileSize(hFile, nullptr);
    bSuccess = nNumBytesToRead != INVALID_FILE_SIZE;
    if (bSuccess) {
      // Done -> read out the INI content
      std::string sxFileContent;
      sxFileContent.resize(nNumBytesToRead);
      DWORD nNumBytesRead;
      bSuccess = 0 != ReadFile(
        hFile,
        const_cast<char*>(sxFileContent.c_str()),
        nNumBytesToRead,
        &nNumBytesRead,
        nullptr
      );
      if (bSuccess) {
        // Done -> look if all of the content is read
        bSuccess = nNumBytesRead == nNumBytesToRead;
        if (bSuccess) {
          // Yes -> look what type of content we have
          m_oFormat = GetExternalStringFormat(sxFileContent, eFormat);

          // Decode it
          CDecodeToUnicodeResult oDecodeResult = DecodeToUnicode(sxFileContent, m_oFormat);
          bSuccess = oDecodeResult.bSuccess;
          if (bSuccess) {
            // Done -> set the content
            m_oContent.SetTo(oDecodeResult.sResult);
          }
        }
      }
    }
  }

  // And return if successfull
  return bSuccess;
}

// Writes to the last used or the given INI file in the specified format
bool CINIFile::Write(LPCWSTR sFilePath) {
  return Write(m_oFormat, sFilePath);
}
bool CINIFile::Write(CExternalUnicodeFormat oFormat, LPCWSTR sFilePath) {
  // Look which file to use
  bool bWriteToOtherFile = sFilePath != nullptr;
  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,
    nullptr,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL
  );
  bool bSuccess = hFile.IsSet();
  if (bSuccess) {
    // Done -> get the INI content to write
    std::wstring sINIContent = m_oContent.GetAll();

    // Possibly encode the INI content before writing it
    CEncodeFromUnicodeResult oEncodeResult = EncodeFromUnicode(sINIContent, oFormat);
    bSuccess = oEncodeResult.bSuccess && !oEncodeResult.bUnknownCharsReplaced;
    if (bSuccess) {
      // Done -> write out the INI content
      DWORD nNumBytesToWrite = oEncodeResult.sxResult.size();
      DWORD nNumBytesWritten;
      bSuccess = 0 != WriteFile(
        hFile,
        oEncodeResult.sxResult.c_str(),
        nNumBytesToWrite,
        &nNumBytesWritten,
        nullptr
      );
      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
const std::wstring& CINIFile::FilePath() const {
  return m_sFilePath;
}

// The format of the file currently in use
const CExternalUnicodeFormat& CINIFile::Format() const {
  return m_oFormat;
}

// Gets the content
CINIContent& CINIFile::Content() {
  return m_oContent;
}
const CINIContent& CINIFile::Content() const {
  return m_oContent;
}