Introduction

My language of choice is VB.NET, because I like its ease of use and rapid development. But neither VB nor the .NET runtime are overly cross-platform friendly. Also, I just like to keep my language proficiencies up to snuff. When I started to dabble in game programming, I quickly realized that I'd need some way to handle *.ini and *.cfg files. I looked around online for a good INI file handling library, but didn't find much. And when I asked around on the programmers' forums, everyone replied that I should just use the built-in GetVal/SetVal functions of the Windows API. This isn't cross-platform friendly programming however, and that just irks me. Besides, those functions are awfully weak and limited - I hate weak and limited.

To be honest, this wasn't my first foray into the world of INI files. I wrote an INI class for VB as well (INI Class using VB.NET). I wanted that same power and flexibility in my C++ programs. And since no one else seemed to be stepping up to the plate, well... here we go.

Background

I thought that coding this class would be a breeze - after all, we are only dealing with text files. But the implementation turned out to be tricky. At first, I tried to read and analyze the file in one fell swoop, but I quickly realized that wasn't going to work. Some INI files are modified by hand, and can be poorly formed. Also, keeping comments and their respective Sections/Keys together was difficult at best. When all was said and done, I realized it would be best to read the file into memory, and keep the various "parts" of the file stored in a struct for easy access and manipulation.

I also had to decide between one of two classic class models - one way to build the class would be to have one CIniFile object represent one file, read all the data in, and do all the operations in memory, and then have the developer call Save() when ready. This is a common way to do things, and does use less overhead. It would look something like this:

I didn't like that approach however, for several reasons. First, it puts the responsibility on the programmer to remember to call Save(), or else all the changes would be lost. Second, it could increase the overhead, if several files were opened, and stayed open for the life of the application. Third, in the event of a crash, all changes again, would be lost. And finally, if changes were made to the file from another source before you called Save(), those changes would be lost. Not good.

The other approach was to use a "fire and forget" model. In that model, all the functions are static - you simply accomplish your mission and exit. That is the model I chose to use, and you can see the code for it below. While this model can create some extra traffic from repeated open/close events, I believe that very few programs access this kind of information so frequently as to make this a concern.

Using the code

I won't paste too much code here, as the class file is heavily commented throughout, and the accompanying demo application contains a demo of every function in the class. But, here is the basic usage of the CIniFile class:

#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");
..

Features

Create, Rename, Edit and Delete Sections and Keys with ease

Comment and Uncomment Sections or Keys

Add comment lines (such as instructions) to any Section or Key

Sort entire files

Can handle multiple comment lines, and comments stay with their targets even through a sort

Retrieve Section Names

Retrieve file contents as a string

Verify that Sections and Keys exist

Limitations

There were a few features I really wanted to add to this class that aren't there yet. First, be aware that this class is case sensitive! I wanted to add an option to ignore case, but was having a tough time implementing it smoothly. My initial solution was to read in the file and convert it all to uppercase for comparison, but this then saved out the file as all uppercase. If you can figure out a way to make it work smoothly, please let me know.

Another option I really wanted to add was the ability to convert INI files to/from XML. While I could technically add this to some degree, it would have either taken an enormous amount of coding on my part, or dependence on another class or library. In the interests of keeping this class cross platform friendly and easily distributable, I decided not to include this feature. If you like, you can probably throw this in using TinyXML or a similar XML parser. Then again, if you are using INI files, then why worry about XML anyway?

Finally, while full featured, this class isn't as robust as it could be. There is almost no error catching. No try/catch blocks, no asserts. The only error checking done is to make sure that the file loads and saves. If it does not, the function gracefully fails, but no error is reported. This code is not heavily tested, there may be bugs.

If you find a bug, or would like to submit enhancements to the code, please contact me.

Update - Sept 23, 2004

I made a few changes to the code and this article. First, I made all the functions static. You no longer need to create an object from the CIniFile class first in order to call the functions. Also, I realized that Record.Commented was being declared as a string - however, it should only ever store either a "#" or a ";". So I made it a char, and then added an enum in order to set the comment character, like this:

Finally, I repacked the zip file - instead of zipping up the entire VS project, I just included the source code files, and a (Windows only) executable of the demo project. This took the file size down to 68k, which should be easier for those with slower connection speeds to stomach.

Share

About the Author

Todd Davis has been working in web and application development for several years, using Silverlight, ASP.NET, VB.NET, C#, C++ and Javascript, as well as a great deal of work with SQL server and IIS.

He currently works for Virtual Radiologic in Eden Prairie, MN, however he is better known for his varied work in the open source community, especially the DotNetNuke project for which he provided several world-renowned training videos and modules. A huge advocate of open source and open knowledge sharing, everything on his website (www.SeaburyDesign.com) is always offered for free.

Whenever he is not actively coding at his laptop (a rarity to be sure), he can be found woodworking, walking with his wife and kids, or motoring along the back roads of MN on his Harley Davidson Fatboy.

Knock yourself out There is no licensing attached, the code is true open source. Please read any comments below as I haven't touched this code in years, and I think some people found some minor issues with it.

I was getting errors with my code so I figured I'll try your test code. I wasn't able to compile on ubuntu. The first error was I could not find tchar.h. Is there something special I need to do to include it?

Sorry, I haven't touched C++ in about 3 years now. I moved to strictly C# after that, which is a different animal altoghether.

I had tried to use standard STD code libraries in this (I think) so it would work on most environments without much modification, but other people have since told me that my code was still not 100% cross-platform. My guess is that you'll need to use type that exists in your libraries. It will probably work with small changes.

Hopefully someone more well versed in C++ than myself will jump in here and help. At the very least, this will serve as a jumping off point for your own code.

EAREP,this code does not #include tchar.h. But it includes stdafx.h, which in turn is very likely to include tchar.h. Stdafx.h is Microsoft's way to deal with precompiled headers and can safely be ignored using gcc.I guess you have a stdafx.h lurking somewhere in your source directory. Just remove the #include "stdafx.h" in the first line and it should work.

I haven't worked with this class for a LONG time, and I don't program in C++ anymore, but - off the top of my head, what I would do is a comment to section B which consists of a blank string - i.e. " " and the should give you a space between the sections.

a few things I would like to see, if someobdy has time to work on it....

1) ability to have spaces/lines in between items in the INI file....basically making it more readable (spacing before each element in each line), as well as interline spacing (i.e. different sections seperated by one space)

2) comments for the functions (i.e. comments as a whole to say what the function does, pre and post conditions)

That is one of the best slams I have read in response to idiot (oops, I mean befuddled) commenters. You are definitely THE MAN. BTW, good article. It is nice to read educated English on this site, at least occasionally.

Just wanted to say, I like this class a lot. It is one of the best Ini manipulation class I have seen so far.

One suggestion though, make methods such as GetSections() and GetSection()return a std::map<> with the KeyName as a key. This would speed even moreretrieval of values with only one disk access. Just my 0.02$ ...

i wanted to use your class but all i know about c++ concerns mfc and it seems that CString cannot be automatically converted into std::string so giving CString parameters in your functions cause error. Can you help me?

I had the same compilation erros, but I have fixed them in another way. Instead of assigning each single attribute, I have created constructors for the struct CIniFile::Record (remember that in C++ "struct" constructions are just like "class" constructions, except by the fact that the former has "public" as default visibility).

I have some comments to your code, I think it is very expensive to use:

Why are all functions static?

Most of the time I want to load/save the INI File once or only very seldom, at startup or when the user changes the preferences. So, simply make it a normal class, make filename and the content vector a member and thats it.

When I want for some reasons to reload/save the data, I can do it with the Load/Save memberfunctions. But there is no need to enforce that it is done automatically.

Some lesser improvements:

1) Please do not add the line:

using namespace std;

to your header file!

With it you defeat the whole idea behind putting something in a namespace. Everywhere youre header is included, the whole namespace gets included.

2)Also, in the header you do not need to include e.g. iostream because you only need it in the cpp file. While this is more of an optimization please try to minimize the headers you include in headers.Everybody who uses a compiler with no precompiled header feature will be grateful.

3) Use const&:

e.g.

static bool AddSection(string SectionName, string FileName);

should get its parameters by const& because there is no need that the strings are copied

4) Errorchecking on streams.

You do no errorchecking if a read access to the inifile fails. While an eror is unlikely you still should protect against it.

Maybe use:

while(!std::getline(inFile, s))){ ... }

if (inFile.eof()) return true;else return false;

This way you could also remove the test for is_open.

5) Do not use endl. For better performance use '\n'. Use endl only if you really intend to flush the stream. You only want this seldom with files.