Introduction to the IniWorker Class
Here is my INI file's parser class. OK, just wait, alright? Just wait before you'll say: "OMG, yet another INI parser, my vote of one". Well, however, if you want to do so, go ahead. A few more words before I start:
- I am giving you a C++ class. However, there are no C++ functions used in the class itself.
And there is one reason for calling this C++:
class IniWorker
{
protected:
private:
public:
IniWorker(){}
~IniWorker(){}
};
So for me - it is C, and only C (with classes). Five lines of code does not define something to be what it is not.
- You can easily turn the whole thing into regular C without a problem.
- I know there are a few or a little bit more then a few INI parsers already out there, and I know that Microsoft advices to not use INI files anymore and rather stick with storing more or less important stuff in the Windows Registry. But still, I prefer INI files. Well, the major case is: on Vista and up, if your app wants to make some changes to the Registry - it should be permitted, so the user gete this popup window. I know a lot of you just turn the UAC off, but. For example, not me. I like to know what and when is trying to access my system, I like to have an eye on everything, or better to say, mostly everything. So I use ini files in my apps very often.
Here are some comments from MSDN anyway, why it is better to use INI files rather than the Registry:
(From http://msdn.microsoft.com/en-us/library/ms724353(v=vs.85).aspx):
Ini files are simple plain text, and easy to read, and there is no risk of damaging another program (as) long as you stick to your INI file in your program's directory.
- If a user has to edit the INI file, the user can do this using any application that can edit text files (Notepad) without having to edit the Registry (dangerous), or load the application itself (which can create catch-22 situations).
- If I ever need to migrate my app to another platform, I can't guarantee a Registry will be available, but I am pretty sure I can read and write text files.
- Moving the app to another machine is easier. In some cases, it might even be as easy as just copying the folder.
- Storing the app settings in an INI file allows multiple versions of an app to be installed on the same machine. With the Registry, this can become difficult or virtually impossible.
As I mentioned before, there are plenty of INI parsers, but maybe that's on the first look, I haven't found anything, let's say, "complete". It is not like I was looking for a solution, just out of curiosity. I have had this class for some time and have used it in many of my projects. So here is a share.
Background
For working with INI files, we got a couple of API available, like:
GetPrivateProfileInt
GetPrivateProfileSection
GetPrivateProfileSectionNames
GetPrivateProfileString
GetPrivateProfileStruct
GetProfileInt
GetProfileSection
GetProfileString
WritePrivateProfileSection
WritePrivateProfileString
WritePrivateProfileStruct
WriteProfileSection
WriteProfileString
From this list, we actually need only those with the "Private" keyword inside. Others, like WriteProfileString
, deal with the Win.ini file only - we are not going to touch this file, and what's more, I couldn't find this file on my system, so it's like, probably something that has been outdated for a while. So shrinking this list, we get:
GetPrivateProfileInt
GetPrivateProfileSection
GetPrivateProfileSectionNames
GetPrivateProfileString
GetPrivateProfileStruct
WritePrivateProfileSection
WritePrivateProfileString
WritePrivateProfileStruct
Now, let's do a simple revision of the collection of tools. Do we need some data integrity checking? Some checksums and stuff? Well, I have never needed any, so I am skipping the following two APIs: GetPrivateProfileStruct
and WritePrivateProfileStruct
. Now, let's consider WritePrivateProfileSection
. From MSDN:
Replaces the keys and values for the specified section in an initialization file.
So if you got data in your INI file like this:
[Section]
key = value
WritePrivateProfileSection
will replace it with a new key and value provided by you. Do we need this? I don't, so I am skipping this API. Now let's take a look at GetPrivateProfileSection
and GetPrivateProfileSectionNames
. You can read their descriptions on MSDN yet again. But let me tell you that these APIs need a statically allocated buffer, and from my own experience, they will not accept anything else, nor will they return the correct size needed for the allocation. Basically, they are implemented in a wrong way. What is more, they return all the values in a single string, like this: something + '\0' + next something + '\0' + whatever + '\0\0'. Well, that is just wrong. That is why I have skipped them too and wrote my own replacements. So shrinking our list yet again, we have now got:
GetPrivateProfileInt
GetPrivateProfileString
WritePrivateProfileString
IniWorker
is based on these three functions. So, let's proceed forward.
Using the IniWorker Class
First off, let me mention IniWorker
's major functionalities:
- 1.a. Class manages a file pointer for you
- 1.b. You can maintain a file pointer, so you can call class methods on your own opened INI file, whatever
- 2.a. Unicode version of methods
- 2.b. ANSI version of methods*
- 3. Additional functionality which is not provided by default by MS APIs
- Garbage collection engine, so you don't have to worry about memory leaks**
*I'll be honest, not every method which is implemented as Unicode has its ANSI alternative. I just do not use ANSI strings in my apps and that is my personal rule. So you have been informed. In case you prefer ANSI and do not need Unicode, you can do some simple conversion. It is just a matter of replacing wchar_t
with char
(wcslen
with strlen
, etc.).
**Well, actually, code leaks some memory (very small amount though), so it's not that PRO. Anyway, you can just remove the GC from the class and free all the allocated memory yourself. That's not a big deal. OK, I access INI in my apps, like max 10 times per run, so this dirty code fits my needs.
Alright, I am talking too much, end of story, period. Let's see a class.
You can see some crazy methods here and there. Let's just focus on the most important things.
IniWorker(IN wchar_t* IniPath,
IN BOOL RelativeToCurrent = FALSE,
IN BOOL UsingCollector = FALSE)
BOOL DeleteSection(IN wchar_t* Section, IN OPT wchar_t *iniPath = NULL);
BOOL SetSimpleOptionBool(IN wchar_t *Section,
IN BOOL Value,
IN OPT wchar_t *iniPath = NULL);
BOOL GetSimpleOptionBool(IN wchar_t *Section, IN OPT wchar_t *iniPath = NULL);
BOOL SetExtendedOptionBool(IN wchar_t *Section,
IN wchar_t *Val,
IN BOOL Value,
IN OPT wchar_t *iniPath = NULL);
BOOL GetExtendedOptionBool(IN wchar_t *Section,
IN wchar_t *Val,
IN OPT wchar_t *iniPath = NULL);
BOOL SetSimpleOptionInt(IN wchar_t *Section, IN int Value,
IN OPT wchar_t *iniPath = NULL);
unsigned int GetSimpleOptionInt(IN wchar_t *Section, IN OPT wchar_t *iniPath = NULL);
BOOL SetExtendedOptionInt(IN wchar_t *Section,
IN wchar_t *Value,
IN int Val,
IN OPT wchar_t *iniPath = NULL);
unsigned int GetExtendedOptionInt(IN wchar_t *Section,
IN wchar_t *Value,
IN OPT wchar_t *iniPath = NULL);
BOOL SetExtendedOption( IN wchar_t* Section,
IN wchar_t* Value,
IN wchar_t* Option,
IN OPT wchar_t *iniPath = NULL);
wchar_t * GetExtendedOption(IN wchar_t* Section,
IN wchar_t* Value,
IN OPT wchar_t *iniPath = NULL);
wchar_t ** GetSections(IN OUT int &elems, IN OPT wchar_t *iniPath = NULL);
wchar_t **GetSectionElements(IN wchar_t* Section,
IN OUT int &elems, IN OPT wchar_t *iniPath = NULL);
wchar_t *GetSectionNameByOptionValue(IN wchar_t *OptionValue,
IN OPT wchar_t *iniPath = NULL);
wchar_t *GetValueNameByOption(IN wchar_t *Option, IN OPT wchar_t *iniPath = NULL);
unsigned int Stats(OUT wchar_t *&CreationDate,
OUT wchar_t *&AccessDate,
OUT wchar_t *&WriteDate,
IN OPT wchar_t *iniPath = NULL);
BOOL Protect(IN BOOL Mode, IN OPT wchar_t *iniPath = NULL)
So nothing really special we got here. Basically, that is what I personally need, just my little share. Alright, here is a testing function:
VOID myFree(LPVOID ptr)
{
if(ptr != NULL)
{
HeapFree(GetProcessHeap(), 0, ptr);
ptr = NULL;
}
}
void IniWorkerTest(int loops, BOOL useCollector)
{
for (int i = 0; i <= loops; i++)
{
wprintf(L"LOOP NR: %d\n", i);
IniWorker *ini = new IniWorker(L"ini.ini", TRUE, useCollector);
ini->SetSimpleOptionBool(L"VistaGlass", TRUE);
ini->SetExtendedOptionBool(L"GlassEffect", L"DisplayText", TRUE);
ini->SetExtendedOption(L"Section1", L"value1", L"option1");
ini->SetExtendedOption(L"Section1", L"value2", L"option2");
ini->SetExtendedOption("Section1", "value3", "option3");
wchar_t *crDate = NULL, *acDate = NULL, *wrDate = NULL;
int fSize = ini->Stats(crDate, acDate, wrDate);
wprintf(L"Ini file size: %d bytes\r\nCreation date: "
L"%s\r\nAccess Date: %s\r\nAnd last write date: %s\r\n",
fSize, crDate, acDate, wrDate);
if(!useCollector)
{
myFree(crDate);
myFree(acDate);
myFree(wrDate);
}
BOOL isVistaGlass = ini->GetSimpleOptionBool(L"VistaGlass");
if(isVistaGlass) wprintf(L"VIsta glass is enabled!\n");
else wprintf(L"vista glass is disabled!\n");
BOOL isGlassEffect_DisplayText =
ini->GetExtendedOptionBool(L"GlassEffect", L"DisplayText");
if(isGlassEffect_DisplayText)
wprintf(L"We should display text on glass!\n");
else
wprintf(L"We shouldnt display text on glass!\n");
wchar_t *ExOptionTest = ini->GetExtendedOption(L"Section1", L"value2");
wprintf(L"Our extended option is %s\n", ExOptionTest);
if(!useCollector)myFree(ExOptionTest);
wchar_t *valName = ini->GetValueNameByOption(L"option2");
wprintf(L"Key = %s\r\n", valName);
if(!useCollector)myFree(valName);
ini->SetSimpleOptionInt(L"TrialTimeout", 100);
ini->SetSimpleOptionInt("AppRuns", 1000);
int TrialTimeout = ini->GetSimpleOptionInt("TrialTimeout");
wprintf(L"TrialTimeout is %d\n", TrialTimeout);
ini->SetSimpleOptionInt("Something", 22);
int Something = ini->GetSimpleOptionInt("Something");
printf("Here is something: %d\n", Something);
ini->SetExtendedOptionInt(L"ExtendedInt", L"runfor", 55);
ini->SetExtendedOptionInt("ExtendedInt", "flushafter", 90);
int Exo = ini->GetExtendedOptionInt(L"ExtendedInt", L"flushafter");
wprintf(L"flushafter = %d\n", Exo);
int Some= ini->GetExtendedOptionInt("ExtendedInt", "runfor");
printf("runfor = %d\n", Some);
int elems = 0;
wchar_t **Array = ini->GetSections(elems);
for (int i = 0; i < elems; i++)
{
wprintf(L"Found section: [%s]\r\n", Array[i]);
}
elems = 0;
wchar_t **Arr = ini->GetSectionElements(L"Section1", elems);
for (int i = 0; i < elems; i++)
{
wprintf(L"Found Value: %s\n", Arr[i]);
}
delete ini;
}
}
So, that's it. For better understanding - just check out the class itself. It would be nice to hear some suggestions on improvements, or if you'll discover some bugs and stuff.
Now goes a small note for "new generation coders": if you would like to suggest that I should use std::string
, use bool
instead of BOOL
, let someone else manage memory for me, put my life in the hands of fate, etcetera - give it a break, I am not using C++ in principle, and will only use it if my further existence on this planet would depend on it; unless it is not, C (with classes) is the only way of samurai.
Cheers!