Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Multiplatform .INI Files


MAR94: Multiplatform .INI Files

Portable profile functions

Joe has been programming for eleven years. He is currently working on an embedded system, writing PC and Windows-based development tools for the embedded OS. He can be reached at [email protected].


Portable-interface toolkits allow you to write code once and recompile with the appropriate libraries for other target platforms. Most of these interface libraries are implemented with a lowest-common-denominator feature subset. This means that if you depend heavily on a specific operating-system feature, the only option available is to develop your own portable version of the routines.

One of the features in the Windows API that I continually rely on is its profile functions, which encapsulate the reading and writing of information to .INI files. For example, I often use .INI files to maintain a list of last-used settings for "smart" dialog boxes. This feature has been so popular in the Windows version of my applications that I implemented the same for my DOS and UNIX programs. The routines presented in this article are portable versions of the Windows functions GetPrivateProfileString(), GetPrivateProfileInt(), and WritePrivateProfileString(). My functions only work with private profile strings for the obvious reason that other operating environments do not have a WIN.INI file.

Reading from the .INI File

To read an entry's value in a section of an .INI file, you use my functions get_private_profile_int() and get_private_profile_string(). Listing One (page 91) presents the header file which contains the prototypes to these functions. The two functions use the same parameters as the equivalent Windows versions, allowing you to incorporate them easily into your existing code. You can use #ifdefs at the point of call, as in Example 1. Alternately, you can use #defines, for example, to make get_private_profile_string resolve to GetPrivateProfileString when compiling for Windows and to remain unchanged when compiling for DOS and UNIX. Thus, you can maintain a single source module for multiple platforms--at least in the case of profile-string manipulations.

Windows .INI files are divided into sections and entries. A section appears within the file as [section_name], followed by any number of entries using the format entry_name=entry_value. For example, the [ports] section of your WIN.INI file contains entries such as COM1:=9600,n,8,1,x.

So the first task in obtaining an entry's value is to find the appropriate section within the file. Your code passes the section name to either of the GetPrivateProfileInt or _String functions as a regular string, for example, "section_name". The Windows function then turns this parameter into "[section_name]". Likewise, get_private_profile_int() and get_private_profile_string() also enclose the section-name parameter within square brackets. Once the section-name string is in the correct format, the routines search the .INI file line by line until a matching section name is found. If one is not found, the value specified in the default parameter will be returned for the integer function; the string function copies the default string into the buffer and returns the default-string length.

If the correct section was found in the .INI file, the profile routines search for a matching entry. The entry name is compared up to the equal sign using the length of the entry parameter. If the search traverses the section without finding the specified entry, the default value or string is returned. If an entry is found, a pointer is set to point to the value portion of the string. The get_private_profile_int() function copies the entry's value string into a temporary string buffer as long as the entry contains numbers, then returns the numeric value of the string using the standard library function atoi(). The get_private_profile_string() function copies the value portion of the entry to the buffer up to a maximum of buffer_len characters. If the entry contains more characters than the buffer can hold, it is truncated to prevent memory overruns. This function returns the number of characters copied into the buffer.

Writing to .INI Files

The Windows profile routines only support the writing of strings to .INI files. To maintain compatibility with the Windows routines, my code has the equivalent limitation. In the effort to keep the code straightforward, my write function creates a temporary file and copies the .INI file to it, as the .INI file is read and searched. At the end, when the file has been completely written, the old .INI file is unlinked and the temporary filename is changed to the supplied filename. This may not be the most efficient method, but it was easy to implement and will be easy to maintain.

The write function tries to open the file for reading first. If it can't do so, the file must not exist, so write creates one and then writes the section information, followed by the entry and its associated string.

The write_private_profile_string() function searches the .INI file for the correct section as with the get functions, but this function copies its input to the temporary file as it goes. If it never finds the section asked for, the function appends the section and entry to the end of the file. If the function finds the correct section, it then searches for the correct entry, copying the file in the process. If the end of the section comes without finding the entry, my routine adds the entry at the end of the section, and the remainder of the file is then copied. In the case where the entry was found, its old value is discarded, replaced by the value passed into the function.

Listing One contains the prototypes for the functions plus the #define MAX_LINE_LENGTH, which is currently set at 80. Listing Two (page 91) implements the functions read_line(), get_private_profile_int(), and get_private_profile_string(). Listing Three (page 91) contains the write_private_profile_string() function.

These functions were designed for ease of reading. There are places where the code could be made smaller by creating subroutines from redundant code, although the savings would be nominal at best. Also, you could write additional functions to handle reading/writing longs, floats, and so on. I didn't implement these additional functions because Windows itself does not support them, and compatibility was of utmost importance. The goal of write-once-port-many is often elusive, but achieving it in this particular case is not too difficult.


Example 1: Calling a routine to read a profile string, using a compile-time conditional to invoke either a Windows version or a portable version.

#ifdef WINDOWS
GetPrivateProfileString(ini_section,ini_entry,default_str,buffer, buffer_len,ini_file);
#else
get_private_profile_string(ini_section,ini_entry,default_str,buffer, buffer_len,ini_file);
#endif

[LISTING ONE]


/***************************************************************************
 PORTABLE ROUTINES FOR WRITING PRIVATE PROFILE STRINGS --  by Joseph J. Graf
 Header file containing prototypes and compile-time configuration.
***************************************************************************/

#define MAX_LINE_LENGTH    80

int get_private_profile_int(char *, char *, int,    char *);
int get_private_profile_string(char *, char *, char *, char *, int, char *);
int write_private_profile_string(char *, char *, char *, char *);


[LISTING TWO]



/***** Routines to read profile strings --  by Joseph J. Graf ******/
#include <stdio.h>
#include <string.h>
#include "profport.h"   /* function prototypes in here */

/*****************************************************************
* Function:     read_line()
* Arguments:    <FILE *> fp - a pointer to the file to be read from
*               <char *> bp - a pointer to the copy buffer
* Returns:      TRUE if successful FALSE otherwise
******************************************************************/
int read_line(FILE *fp, char *bp)
{   char c = '\0';
    int i = 0;
    /* Read one line from the source file */
    while( (c = getc(fp)) != '\n' )
    {   if( c == EOF )         /* return FALSE on unexpected EOF */
            return(0);
        bp[i++] = c;
    }
    bp[i] = '\0';
    return(1);
}
/************************************************************************
* Function:     get_private_profile_int()
* Arguments:    <char *> section - the name of the section to search for
*               <char *> entry - the name of the entry to find the value of
*               <int> def - the default value in the event of a failed read
*               <char *> file_name - the name of the .ini file to read from
* Returns:      the value located at entry
*************************************************************************/
int get_private_profile_int(char *section,
    char *entry, int def, char *file_name)
{   FILE *fp = fopen(file_name,"r");
    char buff[MAX_LINE_LENGTH];
    char *ep;
    char t_section[MAX_LINE_LENGTH];
    char value[6];
    int len = strlen(entry);
    int i;
    if( !fp ) return(0);
    sprintf(t_section,"[%s]",section); /* Format the section name */
    /*  Move through file 1 line at a time until a section is matched or EOF */
    do
    {   if( !read_line(fp,buff) )
        {   fclose(fp);
            return(def);
        }
    } while( strcmp(buff,t_section) );
    /* Now that the section has been found, find the entry.
     * Stop searching upon leaving the section's area. */
    do
    {   if( !read_line(fp,buff) || buff[0] == '\0' )
        {   fclose(fp);
            return(def);
        }
    }  while( strncmp(buff,entry,len) );
    ep = strrchr(buff,'=');    /* Parse out the equal sign */
    ep++;
    if( !strlen(ep) )          /* No setting? */
        return(def);
    /* Copy only numbers fail on characters */

    for(i = 0; isdigit(ep[i]); i++ )
        value[i] = ep[i];
    value[i] = '\0';
    fclose(fp);                /* Clean up and return the value */
    return(atoi(value));
}
/**************************************************************************
* Function:     get_private_profile_string()
* Arguments:    <char *> section - the name of the section to search for
*               <char *> entry - the name of the entry to find the value of
*               <char *> def - default string in the event of a failed read
*               <char *> buffer - a pointer to the buffer to copy into
*               <int> buffer_len - the max number of characters to copy
*               <char *> file_name - the name of the .ini file to read from
* Returns:      the number of characters copied into the supplied buffer
***************************************************************************/
int get_private_profile_string(char *section, char *entry, char *def,
    char *buffer, int buffer_len, char *file_name)
{   FILE *fp = fopen(file_name,"r");
    char buff[MAX_LINE_LENGTH];
    char *ep;
    char t_section[MAX_LINE_LENGTH];
    int len = strlen(entry);
    if( !fp ) return(0);
    sprintf(t_section,"[%s]",section);    /* Format the section name */
    /*  Move through file 1 line at a time until a section is matched or EOF */
    do
    {   if( !read_line(fp,buff) )
        {   fclose(fp);
            strncpy(buffer,def,buffer_len);
            return(strlen(buffer));
        }
    }
    while( strcmp(buff,t_section) );
    /* Now that the section has been found, find the entry.
     * Stop searching upon leaving the section's area. */
    do
    {   if( !read_line(fp,buff) || buff[0] == '\0' )
        {   fclose(fp);
            strncpy(buffer,def,buffer_len);
            return(strlen(buffer));
        }
    }  while( strncmp(buff,entry,len) );
    ep = strrchr(buff,'=');    /* Parse out the equal sign */
    ep++;
    /* Copy up to buffer_len chars to buffer */
    strncpy(buffer,ep,buffer_len - 1);

    buffer[buffer_len] = '\0';
    fclose(fp);               /* Clean up and return the amount copied */
    return(strlen(buffer));
}


[LISTING THREE]



/***** Routine for writing private profile strings --- by Joseph J. Graf *****/
#include <stdio.h>
#include <string.h>
#include "profport.h"

/*************************************************************************
 * Function:    write_private_profile_string()
 * Arguments:   <char *> section - the name of the section to search for
 *              <char *> entry - the name of the entry to find the value of
 *              <char *> buffer - pointer to the buffer that holds the string
 *              <char *> file_name - the name of the .ini file to read from
 * Returns:     TRUE if successful, otherwise FALSE
 *************************************************************************/
int write_private_profile_string(char *section,
    char *entry, char *buffer, char *file_name)

{   FILE *rfp, *wfp;
    char tmp_name[15];
    char buff[MAX_LINE_LENGTH];
    char t_section[MAX_LINE_LENGTH];
    int len = strlen(entry);
    tmpnam(tmp_name); /* Get a temporary file name to copy to */
    sprintf(t_section,"[%s]",section);/* Format the section name */
    if( !(rfp = fopen(file_name,"r")) )  /* If the .ini file doesn't exist */
    {   if( !(wfp = fopen(file_name,"w")) ) /*  then make one */
        {   return(0);   }
        fprintf(wfp,"%s\n",t_section);
        fprintf(wfp,"%s=%s\n",entry,buffer);
        fclose(wfp);
        return(1);
    }
    if( !(wfp = fopen(tmp_name,"w")) )
    {   fclose(rfp);
        return(0);
    }

    /* Move through the file one line at a time until a section is
     * matched or until EOF. Copy to temp file as it is read. */

    do
    {   if( !read_line(rfp,buff) )
        {   /* Failed to find section, so add one to the end */
            fprintf(wfp,"\n%s\n",t_section);
            fprintf(wfp,"%s=%s\n",entry,buffer);
            /* Clean up and rename */
            fclose(rfp);
            fclose(wfp);
            unlink(file_name);
            rename(tmp_name,file_name);
            return(1);
        }
        fprintf(wfp,"%s\n",buff);
    } while( strcmp(buff,t_section) );

    /* Now that the section has been found, find the entry. Stop searching
     * upon leaving the section's area. Copy the file as it is read
     * and create an entry if one is not found.  */
    while( 1 )
    {   if( !read_line(rfp,buff) )
        {   /* EOF without an entry so make one */
            fprintf(wfp,"%s=%s\n",entry,buffer);
            /* Clean up and rename */
            fclose(rfp);
            fclose(wfp);
            unlink(file_name);
            rename(tmp_name,file_name);
            return(1);

        }

        if( !strncmp(buff,entry,len) || buff[0] == '\0' )
            break;
        fprintf(wfp,"%s\n",buff);
    }

    if( buff[0] == '\0' )
    {   fprintf(wfp,"%s=%s\n",entry,buffer);
        do
        {
            fprintf(wfp,"%s\n",buff);
        } while( read_line(rfp,buff) );
    }
    else
    {   fprintf(wfp,"%s=%s\n",entry,buffer);
        while( read_line(rfp,buff) )
        {
             fprintf(wfp,"%s\n",buff);
        }
    }

    /* Clean up and rename */
    fclose(wfp);
    fclose(rfp);
    unlink(file_name);
    rename(tmp_name,file_name);
    return(1);
}


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.