Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

IniWorker

3.86/5 (6 votes)
22 Feb 2011CPOL6 min read 44.7K   490  
Yet another but a little bit different INI files parser.

pic

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:

  1. I am giving you a C++ class. However, there are no C++ functions used in the class itself.
  2. And there is one reason for calling this C++:

    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.

  3. You can easily turn the whole thing into regular C without a problem.
  4. 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.

  1. 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).
  2. 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.
  3. Moving the app to another machine is easier. In some cases, it might even be as easy as just copying the folder.
  4. 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.

pic

You can see some crazy methods here and there. Let's just focus on the most important things.

C++
/*
    Constructor.
    IniPath - is just INI file path
    RelativeToCurrent - if we got our file in the same folder with 
                        our application, or in some subfolder, 
                        then we set this to true.
    UsingCollector - set this to TRUE, if you want the collector to do memory management.
    Usage:
        IniWorker *ini = new IniWorker(L"Settings\\my_app.ini", TRUE, FALSE);
*/
IniWorker(IN wchar_t* IniPath, 
          IN BOOL RelativeToCurrent = FALSE,
          IN BOOL UsingCollector = FALSE)

/*
    Delete section in Ini file. UNICODE and ANSI
    We can provide our own ini file path (wchar_t *iniPath)
    Usage: DeleteSection(L"VistaGlass");
           DeleteSection(L"VistaGlass", L"C:\\my_app.ini");
*/
BOOL DeleteSection(IN wchar_t* Section, IN OPT wchar_t *iniPath = NULL);


/*
    If we just need to set simple option, like: "use vista glass = TRUE"
    Usage: SetSimpleOptionBool(L"VistaGlass", TRUE);
            SetSimpleOptionBool(L"VistaGlass", TRUE, L"C:\\my_app.ini");
    UNICODE and ANSI
*/
BOOL SetSimpleOptionBool(IN wchar_t *Section, 
                         IN BOOL Value,
                         IN OPT wchar_t *iniPath = NULL);


/*
    If we just need to check if some option is true.
    Usage: 
        BOOL useGlass = GetSimpleOptionBool(L"VistaGlass");
        BOOL useGlass = GetSimpleOptionBool(L"VistaGlass", L"C:\\my_app.ini");
    UNICODE and ANSI
*/
BOOL GetSimpleOptionBool(IN wchar_t *Section, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    Usage:
        SetExtendedOptionBool(L"VistaGlass", L"DrawTextOnGlass", TRUE);
        SetExtendedOptionBool(L"VistaGlass", L"DrawTextOnGlass", TRUE, L"C:\\my_app.ini");
*/
BOOL SetExtendedOptionBool(IN wchar_t *Section, 
                           IN wchar_t *Val, 
                           IN BOOL Value,
                           IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    Usage:
        BOOL drawText = GetExtendedOptionBool(L"VistaGlass", L"DrawTextOnGlass");
        BOOL drawText = GetExtendedOptionBool(L"VistaGlass", 
                        L"DrawTextOnGlass", L"C:\\my_app.ini");
*/
BOOL GetExtendedOptionBool(IN wchar_t *Section, 
                           IN wchar_t *Val,
                           IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE AND ANSI.
    If we need to set some simple INT option, like, trial timeout, whatever.
    Usage: 
        SetSimpleOptionInt(L"Timeout", 10);
        SetSimpleOptionInt(L"Timeout", 10, L"C:\\my_app.ini");
*/
BOOL SetSimpleOptionInt(IN wchar_t *Section, IN int Value, 
                        IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE AND ANSI
    Simply, gets an iteger value associated with option.
    Usage:
        int myInt = GetSimpleOptionInt(L"Section");
        int myInt = GetSimpleOptionInt(L"Section", L"C:\\my_app.ini");
*/
unsigned int GetSimpleOptionInt(IN wchar_t *Section, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE AND ANSI.
    Almost like SetSimpleOptionInt. Here a section may contain multiple keys.
    Usage:
        SetExtendedOptionInt(L"Timer", L"TrialTimeout", 10);
        SetExtendedOptionInt(L"Timer", L"TrialTimeout", 10, L"C:\\my_app.ini");
*/
BOOL SetExtendedOptionInt(IN wchar_t *Section, 
                          IN wchar_t *Value,
                          IN int Val,
                          IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    Simply get integer value of some key in INI file.
    Usage:
        int myInt = GetExtendedOptionInt(L"mySection", L"myKey");
        int myInt = GetExtendedOptionInt(L"mySection", L"myKey", L"C:\\my_app.ini");
*/
unsigned int GetExtendedOptionInt(IN wchar_t *Section, 
                                  IN wchar_t *Value,
                                  IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    This is just regular call of WritePrivateProfileString.
    That is what most wrappers offer, so do mine.
    Usage:
        SetExtendedOption(L"VistaGlass", L"DrawTextOnGlass", L"OnlyFor10Seconds");
        SetExtendedOption(L"VistaGlass", L"DrawTextOnGlass", 
                            L"OnlyFor10Seconds", L"C:\\my_app.ini");
*/
BOOL SetExtendedOption(    IN wchar_t* Section, 
                        IN wchar_t* Value, 
                        IN wchar_t* Option,
                        IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI and POINTER COLLECTION (optional)
    This is just regular call of GetPrivateProfileString.
    That is what most wrappers offer, so do mine.
    Usage:
        wchar_t *op = GetExtendedOption(L"VistaGlass", L"DrawTextOnGlass");
        wchar_t *op = GetExtendedOption(L"VistaGlass", 
                          L"DrawTextOnGlass", L"C:\\my_app.ini");
*/
wchar_t * GetExtendedOption(IN wchar_t* Section, 
                            IN wchar_t* Value,
                            IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE ONLY and Array will be freed on delete
    This one here provides some extra functionality. This function will 
    fetch every section name and return all of them inside an array. Plus
    it will return count of sections.
    Usage:
        int count = 0;
        wchar_t **Sections = GetSections(count);
        wchar_t **Sections = GetSections(count, L"C:\\my_app.ini");
        for(int i = 0; i < count; i++)
        {
            wprintf(L"Found section: %s\n", Sections[i]);
        }
*/
wchar_t ** GetSections(IN OUT int &elems, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE ONLY and Array will be freed on delete
    This one here provides some extra functionality. Basically, this function 
    gets all section keys. Like we got:
    [section]
    key = something
    anotherkey = nothing
    Function returns string arrays with section keys and its count.
    Usage:
        int COunt = 0;
        wchar_t **ARray = GetSectionElements(L"section", COunt);
        wchar_t **ARray = GetSectionElements(L"section", COunt, 
                                             L"C:\\my_app.ini");
*/
wchar_t **GetSectionElements(IN wchar_t* Section, 
          IN OUT int &elems, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE ONLY and POINTER COLLECTION (optional)
    This one here provides some extra functionality. So what is value and what is
    option and what is section? Consider this as ini file content:
    [section]
    value = option

    So you got a point. You provide "option" or "value" and get "section".
    Usage:
        wchar_t *secName = GetSectionNameByOptionValue(L"option");
        wchar_t *secName = GetSectionNameByOptionValue(L"value", 
                           L"C:\\my_app.ini");
        The downside of this is you need to have everything unique in  ini file.
        This function simply returns the first find.
*/
wchar_t *GetSectionNameByOptionValue(IN wchar_t *OptionValue, 
        IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE ONLY and POINTER COLLECTION (optional)
    This one here provides some extra functionality. So what is value and what is
    option? Consider this as ini file content:
    [something]
    value = option
    So you got a point. You provide "option" and get "value".
    Usage:
        wchar_t *valName = GetValueNameByOption(L"option");
        wchar_t *valName = GetValueNameByOption(L"option", L"C:\\my_app.ini");
        The downside of this is you need to have everything unique in ini file.
        This function simply returns the first find.
*/
wchar_t *GetValueNameByOption(IN wchar_t *Option, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI and POINTER COLLECTION (optional)
    Some helper function here. Just get our ini file stats, like:
    1. Size; 2. creation date; 3. last access date; 4. Last write date and time
    Usage:
        wchar_t *crDate = NULL, *acDate = NULL, *wrDate = NULL;
        int fileSIze = Stats(crDate, acDate, wrDate);
*/
unsigned int Stats(OUT wchar_t *&CreationDate, 
                   OUT wchar_t *&AccessDate,
                   OUT wchar_t *&WriteDate,
                   IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    if we need to protect our file.
    Well, thats a silly protection, however, will prevent 80% of users from
    messing with our file. 
    Usage:
        Protect(TRUE);
        Protect(TRUE, L"C:\\my_app.ini");
    This function will set or unset the following 
    protection: system file, readonly file, hidden file.
*/
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:

C++
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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)