65.9K
CodeProject 正在变化。 阅读更多。
Home

MFC 和 ANSI C++ INI 读取/写入类 ( Windows \ Linux )

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (47投票s)

2006年2月1日

MIT

7分钟阅读

viewsIcon

323018

downloadIcon

11593

Windows 和 Linux 中面向对象的 INI 文件编辑。

引言

这是对 2009 年编写的代码的重写。最初 (2005 年),CIniFile 对象的底层数据结构是 MFC 的 CList 对象。在处理巨大的 ini 文件时,这效率极低。有人联系我,询问是否有一个可以在 Ansi C++\STL 中工作的版本。在发布了一个可以在 Ansi C++\STL 编译器下不依赖 MFC 编译的版本后,很明显需要进行另一次修订,以去除缓慢的 (MFC)CList 或 (STL)std::list。在 2009 年重写后,代码被修改为使用 std::map ,这极大地提高了性能和效率。然而,底层数据存储的效率仍然存在问题。虽然 2009 年版本的速度比 2005 年的原版好得多,但 std::map 键的存储空间有所浪费,因为每个 CIniSection CIniKey 对象分别存储自己的节名和键名。本版本将现有的 std::map<std::string,CIniSection*>std::map<std::string,CIniKey*> 分别替换为 std::set<CIniSection*>std::set<CIniKey*>。此修改允许节和键名只存储一次,从而大大减少了大型 INI 文件上的内存使用量,同时保持了 O(log(n)) 的效率。在 MFC 项目中使用时,将 CString 传递给 CIniFile 函数时,应将其强制转换为 (LPCTSTR) 或使用 CString GetString() 成员。CIniFile 类根据您的 MFC 项目是 Unicode 还是多字节编译,将 string 存储为 std::wstring std::string 。由于 CString 性能极差且不是 C++ 标准,因此在 2009 年已移除对 CString 的内部使用。当前使用标准 STL 字符串的 CIniFile 类在 MFC 项目中运行良好。应用户要求,最近添加了使用 std::stream CIniFile 对象的功能。为了进一步提高效率,许多函数调用被修改为通过 const std::string&const std::wstring& 传递,以防止不必要的对 std::basic_string 拷贝构造函数的调用。

CIniClass 可以在 MFC 中使用。但是,在将 CString 类型传递给 CIniFile 函数时,有必要调用 CString::GetSting() 方法或将 CString 强制转换为 (LPCTSTR)。以下示例将在下面演示

  • 在 Linux 下使用 Ansi C++\STL 版本的 CIniFile
  • 在 Windows 下使用 Ansi C++\STL 版本的 CIniFile
  • 在 MFC 项目中使用 Ansi C++\STL 版本

提供的示例代码

为了帮助理解如何使用代码,提供了多个版本供下载。提供的下载如下

  • Ansi C++\STL CIniFile (Code Blocks 项目)
  • Ansi C++\STL CIniFile (Visual Studio 6.0 项目)
  • Ansi C++\STL CIniFile 在 Windows MFC (Visual Studio 6.0 项目) 中
  • Ansi C++\STL CIniFile (Visual Studio .NET 2005 项目)
  • Ansi C++\STL CIniFile 在 Windows MFC (Visual Studio .NET 2005 项目) 中

如果您不想要整个项目,可以选择仅下载源码版本。请参阅“在 Windows MFC 项目中使用源码”部分。该部分解释了在使用预编译头文件时需要进行的更改。

效率

在运行在带有 2GB RAM 的 Lenovo T60 上的 Ubuntu Linux 10.04 上进行测试时,CIniFile 可以在大约 2 秒内生成一个包含 10000 个节和每个节 100 个键的 ini 文件并将其写入磁盘。将每个节的键数增加到 1000 后,文件生成时间约为 3 秒。文件长达 10,100,000 行,大小约为 227MB。将数据从文件读回内存也在此范围内。在 MFC 项目中进行了相同的测试。结果不那么令人印象深刻,前一个测试大约需要 3 秒,后一个测试大约需要 30 秒。对于两个测试,节和键都以如下格式写入

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

预处理器定义

以下值适用于 Ansi C++\STL CIniFile 库

注意CIniFile 类目前支持 (STL) std::wstring std::string

CIniFile typedef 取决于是否定义了 _UNICODE 。如果您的项目中定义了 _UNICODE,则 CIniFile typedef CIniFileW ;如果未定义 _UNICODE ,则 CIniFile typedef CIniFileA 对象。CIniFileW 使用 std::wstring 来支持宽字符。请参阅以下预处理器指令

// 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

Visual Studio 下的 CIniFile 库期望在编译时定义 _WIN32 。由于 Windows 不支持 strcasecmp wcscasecmp ,因此定义 _WIN32 会将函数切换为相应的 Windows 版本。这些函数由 std::set 作为自定义比较器使用。

// 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
    }
};

重要的定义

  • _TRACE_CINIFILE - 如果定义,则启用到标准输出的调用跟踪
  • _UNICODE - 如果定义,则 CIniFile 将被定义为 CIniFileW 而不是 CIniFileA
  • _FORCE_UNIX_LINEFEED - 如果定义,当定义了 _WIN32 (WINDOWS) 时,默认行尾 CRLF 将被覆盖为 CR
  • _FORCE_WINDOWS_LINEFEED - 如果定义,当未定义 _WIN32 (*NIX) 时,默认行尾 CR 将被覆盖为 CRLF

解析和行为

目前 CIniFile 被设计为读取大多数 ini 文件。Ini 节应以 "[" 开始并以 "]" 结束。 "[" 和 "]" 之间的空白将被修剪。例如,下面定义的节将被解释为 "SECTION",而不是 " SECTION "。

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

Ini 键\值对应有一个键值,在键值右侧用 "=" 分隔。键值也会被修剪以去除空白。例如,下面定义的键将被解释为 "MyKeyValue=SomeData"。结果键将是 "MyKeyValue",值将是 "SomeData"。

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

然而,Ini 键值不会被修剪,空白会被保留。例如,下面定义的键将被解释为 "MyIniKey= SomeDataWithSpaces "。结果键将是 "MyKeyValue",值将是 " SomeDataWithSpaces "。

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

函数和返回值

以下示例将使用 std::string 作为函数参数。使用 CIniFileA CIniFileW 将分别导致使用 std::string std::wstring CIniFile typedef 基于预处理器指令 _UNICODECIniSection CIniKey 对象的构造函数和析构函数是 private 的。CIniFile 类负责创建 CIniSection 对象。CIniSection 对象负责创建 CIniKey 对象。这种封装可以防止有人删除内部管理的指针。每个子类都有一个指向父对象的指针。CIniKey 有一个指向其父 CIniSection 的指针。CIniSection 有一个指向其关联的 CIniObject 的指针。

// 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)

使用 CIniFile::GetSections 和 CIniSection::GetKeys

以下代码将演示如何使用 GetSection GetKeys 函数。

// 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;
  }
}

使用 CIniMerge Manipulator 和 C++ 流

以下代码将演示如何使用 CIniMerge >> 运算符。

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 - 使用示例

#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 (非 MFC VS6 或 VS 2005.NET) - 使用示例

#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 - 使用示例

#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 - 使用示例

#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;
}

在 Windows MFC 项目中使用源码

要在使用预编译头文件的 MFC 项目中使用 Ansi C++\STL 版本的代码,必须将预编译头文件作为 cinifile.cpp 的第一行添加。请参阅下面的示例

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

未来的增强

也许支持多键值,如另一位 CodeProject 用户在下面的注释中所述。

历史

  • 12\01\2005 - 初始 MFC 发布
  • 01\12\2006 - 移植到 Ansi C++ 非 MFC
  • 06\16\2009 - 添加了对不同行尾类型的支持,解决了读取不同类型行尾的问题
  • 06\17\2009 - 添加了对宽字符的支持
  • 06\21\2009 - 重写以使用 std::map
  • 07\02\2009 - 移除了 MFC 版本,因为 Ansi 版本可以在 MFC 中工作 (提供下载示例)
  • 07\03\2009 - 添加了对 VS6 的支持
  • 07\03\2009 - 修复了 SecMapA \ SecMapW 的问题。命名不针对编码,可能导致问题
  • 07\03\2009 - 修复了 GetKeys GetSections 函数,使其返回 const ref 而不是数据副本
  • 07\14\2009 - 修复了 Load() 在键值上的空格保留
  • 07\26\2009 - 修复了错误的定义 MSC_VER ,应为 _MSC_VER
  • 09\21\2009 - 修复了删除所有节和键的问题,将 empty() 替换为 clear()
  • 09\22\2009 - 添加了重载的 Load() Save() 以读写 streams
  • 09\23\2009 - 添加了用于 streams 的 << >> 运算符
  • 09\24\2009 - 在 Load() 中添加了合并选项
  • 09\25\2009 - 添加了 CIniMerge 用于 <<>>
  • 09\27\2009 - 将 CIniMerge 移至 CIniFile,修复了 VC6 CIniFile::CR 的问题
  • 12\28\2010 - 使用 std::set 而不是 std::map 减少了键存储冗余
  • 12\29\2010 - 减少了传值方法数量以减少深拷贝 std::string 到 const std::string&
  • 05\07\2011 - 将 MSC_VER 修复为 _MSC_VER
  • 05\07\2011 - 修复了 OTHER 文件解析检测问题
  • 05\06\2021 - 许多人联系我,现迁移到 MIT 许可证
© . All rights reserved.