Sign in to follow this  
toddhd

A robust Ini and Cfg file class

Recommended Posts

When I started programming small games, I quickly realized that a class to handle *.ini files would be mighty handy. I didn't see anything online that really impressed me, and the ol' Windows GetVal/SetVal wasn't an answer for me - not only is it not cross platform compatible, it is also extremely limited. So I rolled my own, and posted it here: http://www.codeproject.com/useritems/CIniFile.asp It can: 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 It is not heavily tested yet, and could still use some features, but it should make your life easier if you need a quick and powerful solition to your Ini file probs.

Share this post


Link to post
Share on other sites
It's trivial, but the C++ standard header is #include <cstring>.

Portable, mulit-lingual, case-insensitive comparison is hard.
This is a US solution, to get mulit-lingual I think you need to devel into locales.

struct lt_nocase : std::binary_function<char, char, bool>
{
bool operator()(char x, char y) const
{
unsigned char UMask = ~(1<<5);
if(x>='a' && x<='z')
x&=UMask;
if(y>='a' && y<='z')
y&=UMask;
return x<y;
}
};

struct ci_lexicographical_compare
{
template<typename String>
bool operator()(const String& lhs, const String& rhs) const
{
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), lt_nocase());
}
};

template<typename String>
bool case_insensitive_compare(const String& lhs, const String& rhs)
{
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), lt_nocase());
};

Share this post


Link to post
Share on other sites
I was curious why your example passed the filename to all the functions so I downloaded the source to find out why. Why did you make it load + parse the ini file for every function call? If nothing else, why are these not statics or global/namespace functions if they don't use any member variables?

Share this post


Link to post
Share on other sites
Quote:
Original post by Kibble
I was curious why your example passed the filename to all the functions so I downloaded the source to find out why. Why did you make it load + parse the ini file for every function call? If nothing else, why are these not statics or global/namespace functions if they don't use any member variables?


I had to make a choice when I started writing the library. One way to handle the file data was set it up so that one IniFile object would just handle one file. In other words, you could do something like this:

CIniFile file;
file.Load(filename);
.. do stuff here
file.Save();

This kind of bothered me for several reasons. First, it puts the responsibility on the programmer to remember to Save(). It added to the overhead (admittedly rather small) to have to create seperate CIniFile obj's for each file. And it was difficult to make sure that the data you were dealing with was up to date, and saved in the file (say, if the program crashed before you called Save()).

Anyway, it seemed more efficient to me to create the functions in a "fire-and-forget" format. That way one CIniFile can handle any number of files, and the changes take place right away, and are always up to date.

In regards to your second question - well, I'm not sure what you mean. C++ is admitedly not my first language so to speak, I would consider myself just slightly above noob, and still very noob in some areas. I'm sure this whole class could have been written better. But hey, I figured I'd submit an idea and some code, and its free and open source, so we can all improve on it.

Any suggestions you have to improve or even streamline it, I'd be appreciative of. Go ahead - I'm think skinned (or at least, I thick, or so they tell me) :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Magmai Kai Holmlor
It's trivial, but the C++ standard header is #include <cstring>.

Portable, mulit-lingual, case-insensitive comparison is hard.
This is a US solution, to get mulit-lingual I think you need to devel into locales.
*** Source Snippet Removed ***


I was under the impression that cstring was a MFC thing, which makes in non platform friendly. Am I wrong?

http://www.codeguru.com/forum/showthread.php?s=&threadid=231161

Yes, the case insensitive part was a problem, and I didn't even consider multi-lingual in regards to the case issue. I do think this would be a nice feature, but I sometimes have a tendency to get sidetracked and not finish things, and just wanted to "get the code out there". I'll revise it more in my free time, and as suggestions come in.

For instance, I just realized that the .commented portion of the Record struct is a string. It really should never be more than one character, and it should also be limited to either the # or ; chars. I'll need to ammend that.

Funny, my boss thinks I should be doing something else :)

Share this post


Link to post
Share on other sites
Quote:
Original post by toddhd
I had to make a choice when I started writing the library. One way to handle the file data was set it up so that one IniFile object would just handle one file. In other words, it would look more like this:
CIniFile file;
file.Load(filename);
.. do stuff here
file.Save();

This is the way I would expect a C++ library to behave.
Quote:
This kind of bothered me for several reasons. First, it puts the responsibility on the programmer to remember to Save(). It added to the overhead (admittedly rather small) to have to create seperate CIniFile obj's for each file. And it was difficult to make sure that the data you were dealing with was up to date, and saved in the file (say, if the program crashed before you called Save()).

More overhead than loading and parsing the file for every function call? Memory is pretty cheap, and this class wouldn't be very large.

If you don't want the responsibility to remember to save, just call save in the destructor:

class CIniFile
{
...
public:
~CIniFile() // is called whenever the object is destroyed (goes out of scope, deleted, ...)
{
Save();
}

void Save()
{
// save the file
}
};

Also, if the program crashes, its probably not a good idea to save settings that potentially caused the crash in the first place. I thought about this when I wrote my configuration stuff. There is a slim chance that whatever caused the crash is a bad setting, so I decided not to save settings on crashes.
Quote:
Anyway, it seemed more efficient to me to create the functions in a "fire-and-forget" format. That way one CIniFile can handle any number of files, and the changes take place right away, and are always up to date.

I would disagree
Quote:
In regards to your second question - well, I'm not sure what you mean. C++ is admitedly not my first language so to speak, I would consider myself just slightly above noob, and still very noob in some areas. I'm sure this whole class could have been written better. But hey, I figured I'd submit an idea and some code, and its free and open source, so we can all improve on it.

Any suggestions you have to improve or even streamline it, I'd be appreciative of. Go ahead - I'm think skinned (or at least, I thick, or so they tell me) :)

You used the STL very well, which is the reason I asked in the first place :) If it was me I would definitely make it a class that loads and saves once, where one instance = one ini file that is in use. Otherwise, you might as well make all the functions static or make it a namespace, then you would use it like this:

// Create an instance of the CIniFile class
CIniFile::Create("test.ini"); // Create a new file called test.ini
CIniFile::SetValue("MyKey","MyValue","MySection",FileName); // Create a Section called MySection, and a key/value of MyKey=MyValue
std::string s = CIniFile::GetValue("MyKey","MySection",FileName);// Get the value of MyKey in section MySection and store it in s
CIniFile::Sort("test.ini");


edit: Wow I really butchered that post. The edit box is too small ;)

Share this post


Link to post
Share on other sites
I realise this might be way off the mark, but how about ditching .cfg and .ini files and going for XML?

There's libraries out there that will do the donkey work for you, all you need to do is structure the XML file to fit around your config options or settings and use XPath (or other) to retrieve each one.

I only suggest this because that's what I use. However, I use C# so what do I know [wink]

Share this post


Link to post
Share on other sites
Quote:
Original post by zdlr
I realise this might be way off the mark, but how about ditching .cfg and .ini files and going for XML?

There's libraries out there that will do the donkey work for you, all you need to do is structure the XML file to fit around your config options or settings and use XPath (or other) to retrieve each one.

I only suggest this because that's what I use. However, I use C# so what do I know [wink]


XML is great, but there are already a bunch of XML parsers out there. I didn't need to write another one. The nice thing about Ini files is that they are easy to read and understand by pretty much anyone. I've done a lot of "support" work over the years, and have had to explain to non-computer-people how to tweak their ini files over the phone. All I can say is that I wouldn't want to try and explain how to tweak their XML! :)

I was just very surprised, when I started learning C++, that there wasn't some "standard" class for this stuff, seeing as how it was (and still is) the way many programs have stored info over the years. An yet people keep "rolling their own" over and over.

Share this post


Link to post
Share on other sites
Quote:
Original post by toddhd
XML is great, but there are already a bunch of XML parsers out there. I didn't need to write another one.

...

An yet people keep "rolling their own" over and over.


Hehehe. Fair enough, if you need .ini files for some specific reason then go ahead, just make sure you let me use your source code when you're done [smile]

Share this post


Link to post
Share on other sites
Kibble,

Thanks for the input - good points, all. Now I see what you mean by making them static, probably how I should have done it considering the way it works (fire and forget).

Personally I see both approaches as having both good and bad points. I like the way it was written, but I also like to make things "standard" and "extensible", and above all, follow the KISS paradigm. I'll need to spend some more time studying how files are handled in C++ and other languages, and model after that.

Share this post


Link to post
Share on other sites
About XML, the way I do pretty much everything in my programs is have an interface for whatever, then implement it one or more ways. So in the case of Configuration files:


class IConfig
{
public:
...
virtual bool ReadSetting(const TCHAR * Setting, std::string & Value) = 0;
virtual bool ReadInt(const TCHAR * Setting, int & Value)
{
std::string StrValue;
if(ReadSetting(Setting, StrValue))
...
return false;
}
virtual void WriteSetting(const TCHAR * Setting, const TCHAR * Value) = 0;
...
};

class CIniConfig : public IConfig
{
// use .ini files
};

class CXmlConfig : public IConfig
{
// use .xml files
};

and in the platform specific DLL that I use, I also have:

class CRegistryConfig : public IConfig
{
// use windows registry
};

That way you can change if you want, without changing a lot of code (pretty much none, aside from the creation of the config object).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this