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

C++ 的 CIniFile 类 - 一个健壮的跨平台 INI 文件类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (38投票s)

2004 年 9 月 21 日

CPOL

5分钟阅读

viewsIcon

238663

downloadIcon

7386

一种功能齐全且对跨平台友好的处理 *.ini 和 *.cfg 文件的方式。

引言

我选择的语言是 VB.NET,因为它易于使用且开发迅速。但 VB 和 .NET 运行时都不是非常跨平台友好。而且,我只想保持我的语言熟练度。当我开始涉足游戏编程时,我很快意识到我需要某种方法来处理 *.ini*.cfg 文件。我在网上寻找一个好的 INI 文件处理库,但没找到太多。当我询问程序员论坛时,每个人都回答说我应该直接使用 Windows API 的内置 GetVal/SetVal 函数。但这并非跨平台友好的编程,这让我很恼火。此外,这些函数非常弱且受限——我讨厌弱且受限。

说实话,这不是我第一次涉足 INI 文件世界。我也为 VB 编写了一个 INI 类(使用 VB.NET 的 INI 类)。我希望在我的 C++ 程序中也拥有同样的强大功能和灵活性。而且,既然似乎没有人主动站出来,那么……我们开始吧。

背景

我以为编写这个类会很容易——毕竟,我们只是处理文本文件。但实现结果却很棘手。起初,我试图一次性读取和分析文件,但我很快意识到那样行不通。有些 INI 文件是手动修改的,并且可能格式不正确。另外,将注释及其各自的 Section/Key 放在一起非常困难。最终,我意识到最好将文件读入内存,并将文件的各个“部分”存储在 struct 中,以便于访问和操作。

我还需要在两个经典的类模型之间做出选择——一种构建类的方式是让一个 CIniFile 对象代表一个文件,读取所有数据,并在内存中进行所有操作,然后让开发人员在准备好时调用 Save()。这是一种常见的做法,并且开销较小。它看起来像这样:

// Create an instance of the CIniFile class
CIniFile IniFile;
// Create a new file called test.ini
IniFile.Create("test.ini");
// Create a key
IniFile.SetValue("MyKey","MyValue","MySection",FileName);
// Save the changes
IniFile.Save();

然而,我因为几个原因不喜欢这种方法。首先,它将责任推给了程序员,让他们记住调用 Save(),否则所有更改都将丢失。其次,如果打开了几个文件并在应用程序生命周期内保持打开状态,可能会增加开销。第三,在发生崩溃的情况下,所有更改同样会丢失。最后,如果你在调用 Save() 之前从其他源更改了文件,这些更改将丢失。不好。

另一种方法是使用“即时生效”(fire and forget)模型。在该模型中,所有函数都是静态的——你只需完成你的任务即可退出。这就是我选择使用的模型,你可以在下面看到它的代码。虽然这种模型可能会因为重复的打开/关闭事件而产生一些额外的流量,但我相信很少有程序会如此频繁地访问此类信息,以至于使其成为一个令人担忧的问题。

使用代码

我不会在这里粘贴太多代码,因为类文件在整个过程中都带有大量的注释,并且附带的演示应用程序包含该类中每个函数的演示。但是,这是 CIniFile 类的基本用法:

#include "CIniFile.h"
#include <string.h>
..
// Create a new file called test.ini
CIniFile::Create("test.ini");
// Create a Section called MySection, and a key/value of MyKey=MyValue
CIniFile::SetValue("MyKey","MyValue","MySection",FileName);
// Get the value of MyKey in section MySection and store it in s
std::string s = CIniFile::GetValue("MyKey","MySection",FileName);
// Sort the sections and records
CIniFile::Sort("test.ini");
..

特点

  • 轻松创建、重命名、编辑和删除 Section 和 Key
  • 注释和取消注释 Section 或 Key
  • 为任何 Section 或 Key 添加注释行(例如说明)
  • 对整个文件进行排序
  • 可以处理多行注释,并且注释在排序过程中会保留在目标旁边
  • 检索 Section 名称
  • 将文件内容检索为字符串
  • 验证 Section 和 Key 是否存在

限制

我真的很想在这个类中添加一些尚未实现的功能。首先,请注意,这个类是**区分大小写**的!我想添加一个忽略大小写的选项,但很难顺利实现。我最初的解决方案是读取文件并将其全部转换为大写进行比较,但这会导致文件以全大写形式保存。如果你能找到一种平滑实现的方法,请告诉我。

我真正想添加的另一个选项是将 INI 文件转换为/从 XML 转换。虽然我可以在一定程度上添加它,但这要么需要我付出大量的编码工作,要么依赖于另一个类或库。为了保持这个类跨平台友好且易于分发,我决定不包含此功能。如果你愿意,你可能可以使用 TinyXML 或类似的 XML 解析器来完成。再说,如果你正在使用 INI 文件,为什么要担心 XML 呢?

最后,虽然功能齐全,但这个类不如它本可以的那样健壮。几乎没有错误处理。没有 try/catch 块,没有断言。唯一进行的错误检查是确保文件加载和保存。如果失败,函数会优雅地退出,但不会报告任何错误。此代码未经大量测试,可能存在 bug。

如果你发现 bug,或想提交对代码的增强,请联系我。

更新 - 2004 年 9 月 23 日

我对代码和这篇文章做了一些更改。首先,我将所有函数都设为静态。你不再需要先创建 CIniFile 类的对象才能调用函数。另外,我意识到 Record.Commented 被声明为字符串——然而,它只需要存储“#”或“;”。所以,我将其更改为 char,然后添加了一个 enum 来设置注释字符,如下所示:

CIniFile::CommentRecord(CIniFile::CommentChar::Pound, 
                            "MyKey","YourSection",FileName)

最后,我重新打包了 zip 文件——不再压缩整个 VS 项目,而是只包含源代码文件和一个(仅 Windows)演示项目的可执行文件。这使得文件大小降至 68k,对于连接速度较慢的用户来说更容易接受。

© . All rights reserved.