CSettings
A quick programming tutorial

Table of Content

  1. Introduction
  2. Getting Started
    1. Working With Pointer Data Types
    2. Working With Other Data Types
  3. Beyond The Basics
    1. Building a Simple Address Book Program
  4. Feedback

Introduction

This is an easy tutorial for programmers wanting to use the CSettings class. It explains basic ways of using objects of this class. It's written in C++, thus solid knowledge of the language is required. You should know how to work with templates. This tutorial will not cover all the technical features CSettings offers to you, please refer to the documentation for futher details. Please note that

typedef unsigned int uint;

is used throughout the code.

Getting Started

This is the easy part. Just untar the CSettings class's files into a directory in your project's source tree and add the files to your project.

In order to use CSettings you must include the "settings.h" header which holds the definition of the class and will include all other necessary headers. These are:

#include <stdio.h>
#include <cassert>
#include <iostream>
#include <map>
#include <vector>

CSettings works best for me when it's used as a global object that any part of my program can use. When the CSettings object is created by the constructor CSettings(const char *str); checks for a file named *str and if it finds one it will check whether the file is a valid settings file. If no file is found or the found file is invalid, a new file will be created. If you don't want to overwrite existing files that are not a settings file, you must dynamically check if they exist and choose a different filename if needed.

Working With Pointer Data Types

This is the really simple part. First you have to have a CSetting's object, create an enum to hold the indexes, and then make a call to uint SetPointerData(const void *data, uint size, int setting = -1);. See an example:

#include <iostream>
#include <string>
#include "settings.h"

using namespace std;

CSettings g_settings;

enum {
    user_name = 0,
    user_age,
    last_enum_value
};

void foo()
{

[...]

    cout << "Welcome. Please Enter Your Name: " ;
    string username;
    cin >> username;

    g_settings.SetPointerData(username.c_str(), username.size(), user_name);

[...]

}

The first argument of SetPointerData() should be a pointer to the data you want to store. The second is the length of the data in bytes. CSettings is designed to store any data you provide not only character strings. This is why it cannot rely on an internal strlen() call to determine the data's length. The final optional argument is used by CSettings as an index value for the data stored. You will need this value to retrieve the contents of your data. As mentioned this argument is optional, if not set, a new index value will be allocated. The return value is always the index used to store the data, whether it has been set or a new one has been just allocated. Refer to the documentation for detail.

void foo()
{

[...]

    cout << "Previous Users Include:" << endl;
    uint str_size;

    int i(0);
    for (const void *str =
        g_settings.GetPointerData(i * last_enum_value + user_name,
        &str_size); str; ++i, str =
        g_settings.GetPointerData(i * last_enum_value + user_name,
        &str_size)) {
            if (i)
                cout <<  ",";
            cout << (const char*)str;
    }

[...]

}

When you need to retrieve data previously stored in the CSettings object you use const void* GetPointerData(uint setting, uint *sizeReturn = 0);. The first argument is the setting's index and the second optional argument in a pointer to an unsigned int where the data's size will be returned. If the second argument is not set or is equal NULL, no information about the size of the data is returned. The return value is a pointer to the data you requested. If the index you have set as the first argument holds no data, zero will be returned. Refer to the documentation for detail.

Working With Other Data Types

This is where the best part to CSettings. It uses templates to give you the ability to store any data type that's size is known at compile time. This is done by two functions.

uint Set(tName val, int setting = -1)
bool Get(tName& type, uint id)

The first function as the name suggests is used to set values of any data types, the second is used to retrieve them. These functions are described in detail in the documentation.

If you have any class (data type) whether it's a POD data type or a complex class with hundreds of members - you can use uint Set(tName val, int setting = -1) to store it in memory. The first argument is the data itself. The second is optional an should be an index number under which the data should be stored. If it is not set, a new index number will be allocated. The return value is always an index number under which the data has been stored.

If you want to retrieve any value you must use void Get(tName& val, uint id). The first argument is a reference to the variable you want to retrieve the data to. It is important that the variable you pass the reference to is the same size as the variable that was used when calling Set(). The second argument is the index number under which the value is stored. The return value indicates success (true) of failure (false). Please refer to the documentation for more details.

#include <iostream>
#include <string>
#include "settings.h"

using namespace std;

CSettings g_settings;

enum {
    run_time = 0
};

void bar()
{

[...]

int runtime(0);

g_settings.Get(runtime, run_time);
++runtime;
g_settings.Set(runtime, run_time);

cout << "Running for the " << runtime << " time" << endl;

[...]

}

Beyond The Basics

When you make calls to the Set and Get functions, the CSettings object responsible for holding your data will load it into memory from the file it uses for storage. This might be a performance hit when loading lots of data. The solution to this is the LoadData() function. You can call it at any time to make CSettings load all the data into memory.

If you feel that the settings are taking up too much memory you can free it by calling ClearMemory(). Please note that in order to use Set* and Get* functions after clearing the memory the data will have to be loaded once again either automatically when making calls to Set or Get, or by calling LoadData().

If you want to make CSettings write the data it holds to the settings file use Sync(). This function should be called prior to terminating the program in a way in which the objects deconstructor would not be called.

If you feel like deleting all the data that the CSettings object holds you can call DropData();. This will call ClearMemory(), and prevent automatic data loading. You can still call LoadData to retrieve all data previously synchronized with the settings file.

Building a Simple Address Book Program

Lets see how easy it is to create a simple address book program when using the CSettings class to handle disk storage of the data. We begin our task by defining what fields will each entry consist of. We create a simple enum that describes our needs. These might include:


enum {            // string enum values
    full_name = 0,
    job_title,
    organization,
    address,
    www_address,
    email_address,
    phone_number,
    last_string_enum_value
};

enum {                // bool enum values
    wants_HTML_mail = 0,
    last_bool_enum_value
};

The second thing we need to do is create a class to hold all the data from an entry. This could look something like this:


class entry {
    entry(); // default constructor

    char *full_name;
    char *job_title;
    char *organization;
    char *address;
    char *www_address;
    char *email_address;
    char *phone_number;
    bool wants_HTML_mail;
};

entry::entry() : wants_HTML_mail(false)
{
    full_name = 0;
    job_title = 0;
    organization = 0;
    address = 0;
    www_address = 0;
    email_address = 0;
    phone_number = 0;
}

The next thing we will do is create a read and write procedure for such a data entry using CSettings. Lets start of with the write function:

bool write_entry(const entry *e, uint i)
// first argument is the entry itself, second is the index number
// return type is bool to indicate success (true) or failure (false)
{
    if (e->full_name) {
        g_settings.SetPointerData(e->full_name, strlen(e->full_name),
            last_string_enum_value * i + full_name);
    } else
        return false;   // this is because we always want
                        // an entry to contain a full_name

    if (e->job_title)
        g_settings.SetPointerData(e->job_title, strlen(e->job_title),
            last_string_enum_value * i + job_title);

    if (e->organization)
        g_settings.SetPointerData(e->organization, strlen(e->organization),
            last_string_enum_value * i + organization);

    if (e->address)
        g_settings.SetPointerData(e->address, strlen(e->address),
            last_string_enum_value * i + address);

    if (e->www_address)
        g_settings.SetPointerData(e->www_address, strlen(e->www_address),
            last_string_enum_value * i + www_address);

    if (e->email_address)
        g_settings.SetPointerData(e->email_address, strlen(e->email_address),
            last_string_enum_value * i + email_address);

    if (e->phone_number)
        g_settings.SetPointerData(e->phone_number, strlen(e->phone_number),
            last_string_enum_value * i + phone_number);

    g_settings.Set(e->wants_HTML_mail,
        last_bool_enum_value * i + wants_HTML_mail);

    return true;
}

Now lets see the read function:

bool read_entry(entry *e, uint i)
// first argument is the entry itself, second is the index number
// return type is bool to indicate success (true) or failure (false)
{
    e->full_name = g_settings.GetPointerData(
        last_string_enum_value * i + full_name);

    e->job_title = g_settings.GetPointerData(
        last_string_enum_value * i + job_title);

    e->organization = g_settings.GetPointerData(
        last_string_enum_value * i + organization);

    e->address = g_settings.GetPointerData(
        last_string_enum_value * i + address);

    e->www_address = g_settings.GetPointerData(
        last_string_enum_value * i + www_address);

    e->email_address = g_settings.GetPointerData(
        last_string_enum_value * i + email_address);

    e->phone_number = g_settings.GetPointerData(
        last_string_enum_value * i + phone_number);

    g_settings.Get(e->want_HTML_mail,
        last_bool_enum_value * i + wants_HTML_mail);

    return true;
}

Now we will make a global function to load all the entries at the begining and write them at the end of the program. These could be implemented like this:


std::vector entry_data;

void read_all_entries()
{
    g_settings.LoadData();
    entry *e;
    uint d_size = g_settings.GetSize(sizeof(bool));

    entry_data.reserve(d_size);
    for (int i(0); i < d_size; ++i) {
        e = new entry();
        if (read_entry(e, i)) {
            entry_data.push_back(e);
        }
    }
    g_settings.ClearMemory();

}

void write_all_entries()
{
    g_settings.LoadData();

    uint d_size = entry_data.size();

    for (int i(0), int a(0); i < d_size; ++i) {
        if (write_entry(entry_data[i], a))
            ++a;

        // some other delete call
        // like:
        // if (entry_data[i]->fullname)
        //     delete [] entry_data[i]->fullname;
        // etc.

        // delete entry_data[i];
    }
}

Now you need to create a simple UI to give your users the choice to add, edit, or delete an entry from the address book. When adding an entry you will simply have to create a new entry class, fill it with data and use push_back() to add it to the global vector holding all the entries. If you delete entries, you should delete the entry from the global vectory by using erase(), use DropData() to clear the internal memory of the CSettings object and write data with write_all_entries(). Editing entries can be done by calling write_entry(). If you want to modify the entry data you should create a copy constructor for the entry object, and always copy the data to a new location in memory, because all the pointers in the entry class refer to memory that belongs to the CSettings object.

Feedback

If you have any questions or something in this tutorial is unclear please email me.


"The population grows - the total intelligence remains constant."

Last update: Wednesday, 11th October, 2023
Copyright © 2001-2024 by Lukasz Tomicki