• Welcome to Jose's Read Only Forum 2023.
 

ProgEx35 -- Random Binary File Access Using Standard C Library Functions

Started by Frederick J. Harris, November 29, 2009, 01:28:33 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris


/*
  ProgEx35 -- Random Binary File Access

  For this lets start with the Employee struct we've been working with for some
  time now.  Up to now I havn't discussed the fopen() stdio.h function very
  much, but now its time to look at it.  The first parameter is a char* param
  that constitutes the path of the file to open.  The second parameter is termed
  mode and its the tricky one.  In fact its so tricky that over the years I've
  gradually moved away from using the C runtime file functions and I've
  gravitated to the Win32 Api functions.  However, the C runtime functions are
  highly usable so I want to cover them.  I've decided to just list Microsoft's
  description of their fopen() mode arguement...

  The character string mode specifies the type of access requested for the file,
  as follows:

  "r"  Opens for reading. If the file does not exist or cannot be found, the
       fopen call fails.

  "w"  Opens an empty file for writing. If the given file exists, its contents
       are destroyed.

  "a"  Opens for writing at the end of the file (appending) without removing the
       EOF marker before writing new data to the file; creates the file first if
       it doesn't exist.

  "r+" Opens for both reading and writing. (The file must exist.)

  "w+" Opens an empty file for both reading and writing. If the given file
       exists, its contents are destroyed.

  "a+" Opens for reading and appending; the appending operation includes the
       removal of the EOF marker before new data is written to the file and the
       EOF marker is restored after writing is complete; creates the file first
       if it doesn't exist.  When a file is opened with the "a" or "a+" access
       type, all write operations occur at the end of the file. The file pointer
       can be repositioned using fseek or rewind, but is always moved back to
       the end of the file before any write operation is carried out. Thus,
       existing data cannot be overwritten.

       The "a" mode does not remove the EOF marker before appending to the file.
       After appending has occurred, the MS-DOS TYPE command only shows data up
       to the original EOF marker and not any data appended to the file. The
       "a+" mode does remove the EOF marker before appending to the file. After
       appending, the MS-DOS TYPE command shows all data in the file. The "a+"
       mode is required for appending to a stream file that is terminated with
       the CTRL+Z EOF marker.

       When the "r+", "w+", or "a+" access type is specified, both reading and
       writing are allowed (the file is said to be open for "update"). However,
       when you switch between reading and writing, there must be an
       intervening fflush, fsetpos, fseek, or rewind operation. The current
       position can be specified for the fsetpos or fseek operation, if desired.

                               --End Microsoft--

       Lets start with a simple program to just write those two Employee records
       we've been playing with all along to a random access file.  That will
       give you a view of the fwrite() and fread() functions.  These are
       something like BASIC's Get and Put statements.  They are prototyped like
       so...

       size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
       size_t fread(void* buffer, size_t size, size_t count, FILE* stream);

       Parameters

       buffer      Storage location for data
       size        Item size in bytes
       count       Maximum number of items to be written/read
       stream      Pointer to FILE structure

       size_t is a 4 byte quantity on 32 bit systems.  To start, we'll assume
       we're creating a new file that does not previusly exist, so our fopen()
       will look like this...

       fp=fopen("Data.dat","wb");

       I've used "wb" for mode.  The "w" is to write of course, and the "b"
       means binary mode.  With this mode setup any previously existing Data.dat
       would be destroyed. 

       The fwrite() function can easily write 1 or more records to the file.  In
       the program below we have this call pertaining to our 2 Employee records
       emp[2]...

       iCount = fwrite(emp, sizeof(Employee), 2, fp);

       Note that the 1st parameter of the function takes a pointer, and I've
       said many times here that an array name without the brackets is a base
       pointer to the array, so you see we just use 'emp'.  If our declaration
       of the Employee struct/type was just...

       Employee emp;

       and we were just writting that one record, the call would look like
       this...

       iCount = fwrite(&emp, sizeof(Employee), 1, fp);

       The iCount variable just receives the number of records written, so it is
       useful for error testing.
*/
#include <stdio.h>
#include <string.h>

struct Employee          //Same as Type Employee
{
char szName[32];   
char szAddress[32];
char szCity[16];
char szState[16];
char szCountry[16];
char szZipCode[16];
unsigned int iAge;
unsigned int iEmpNum;
double dblSalary;   
};                       //End Type

void Initialize(Employee* pEmp)
{
strcpy(pEmp[0].szName,"Martin Veneski");          //1st employee
strcpy(pEmp[0].szAddress,"1016 Wabash Street");
strcpy(pEmp[0].szCity,"Shamokin");
strcpy(pEmp[0].szState,"PA");
strcpy(pEmp[0].szCountry,"USA");
strcpy(pEmp[0].szZipCode,"17872");
pEmp[0].iAge=23;
pEmp[0].iEmpNum=123456;
pEmp[0].dblSalary=45000;

strcpy(pEmp[1].szName,"Ray Latchkoski");         //2nd employee
strcpy(pEmp[1].szAddress,"1624 Tioga Street");
strcpy(pEmp[1].szCity,"Shamokin");
strcpy(pEmp[1].szState,"PA");
strcpy(pEmp[1].szCountry,"USA");
strcpy(pEmp[1].szZipCode,"17872");
pEmp[1].iAge=25;
pEmp[1].iEmpNum=789123;
pEmp[1].dblSalary=50000;
}

int main(void)
{
Employee emp[2];
FILE* fp=NULL;
int iCount=0;

Initialize(emp);
fp=fopen("Data.dat","wb");                   //"wb" for a mode parameter will
if(fp)                                       //open the file in binary mode and
{                                            //destroy any existing contents of
    puts("File Apparently Opened!");          //a file named Data.dat.  The 3rd
    iCount=fwrite(emp,sizeof(Employee),2,fp); //parameter of the fwrite call is
    printf("iCount = %u\n",iCount);           //2 so we're writting both emp[]
    fclose(fp);                               //records to the file.
}
else
{
    puts("Couldn't Open File!");
    return -1;
}

memset(emp,0,sizeof(Employee)*2);            //Erase the existing data in emp[]
fp=fopen("Data.dat","rb");                   //with memset().  Then use "rb" or
if(fp)                                       //'read binary' as a mode param.
{                                            //If the file Data.dat doesn't
    puts("File Apparently Opened!");          //exist the fopen() call will
    iCount=fread(emp,sizeof(Employee),2,fp);  //fail.  We'll read both records
    printf("iCount = %u\n\n",iCount);         //back into emp[] with fread(),
    fclose(fp);                               //but we'll need a loop for output
    puts("Rec#    Name            Address                 City            State   Country ZipCode Age     EmpNum  Salary");
    puts("=================================================================================================================");
    for(unsigned int i=0; i<2; i++)
    {
        printf
        (
         "%u\t%s\t%s\t%s\t%s\t%s\t%s\t%u\t%u\t%6.2f\n",
         i,
         emp[i].szName,
         emp[i].szAddress,
         emp[i].szCity,
         emp[i].szState,
         emp[i].szCountry,
         emp[i].szZipCode,
         emp[i].iAge,
         emp[i].iEmpNum,
         emp[i].dblSalary
        );
    }
}
else
    puts("Couldn't Open File!");
getchar();

return 0;
}

/*
File Apparently Opened!
iCount = 2
File Apparently Opened!
iCount = 2

Rec#    Name            Address                 City            State   Country ZipCode Age     EmpNum  Salary
=================================================================================================================
0       Martin Veneski  1016 Wabash Street      Shamokin        PA      USA     17872   23      123456  45000.00
1       Ray Latchkoski  1624 Tioga Street       Shamokin        PA      USA     17872   25      789123  50000.00
*/

Frederick J. Harris


/*
  ProgEx35b -- Random (binary) File Access Continued

  In this example lets use the Data.dat file produced by the last example that
  just contains our two Employee records of Marty and Ray.  But lets open the
  file in "rb+" mode which is read/write mode for existing files only, and try
  some editing in terms of updates and additions.  We'll output the contents of
  the first record, then update it with a new record.  Then we'll add a third
  record onto the end of the file.  Finally we'll dump the data.

  In the code below I've added my home-made RecordCount function because the
  Standard C Library functions don't seem to have one.  Also, I've included
  wrapper functions around the fseek() / fead() and fseek() / fwrite() functions
  in terms of BASIC like Get() / Put() syntax. 

  As I mentioned, use the Data.dat file from the last example containing our
  two records for this.  If we don't use an existing file it won't work because
  we're running in "rb+" mode (file needs to exist).
*/
#include <stdio.h>
#include <string.h>

struct Employee   
{
char szName[32];   
char szAddress[32];
char szCity[16];
char szState[16];
char szCountry[16];
char szZipCode[16];
unsigned int iAge;
unsigned int iEmpNum;
double dblSalary;   
};

/* ************************************************************************* */
// The C Standard Library doesn't really have a record count function for
// binary/random access flat files so one has to be made from fseek() and
// ftell().  this...
//
// fseek(fp,0,SEEK_END);
//
// will position the file pointer at the end of the file (feof()), and fTell
// will return the byte position at that point.  The byte offset at the feof
// divided by the length of a struct being read/written will yield the count
// of records.  Note that the function preserves the file pointer position.
/* ************************************************************************* */
long RecordCount(FILE* fp, long iRecLength)
{
long iTmp=0, iReturn=0, iLocation=0;

iTmp=ftell(fp);                 //Temporarily store where caller was at
if(iTmp==-1L)                   //If err return with error code
    return iTmp;                 //Something went wrong
iReturn=fseek(fp,0,SEEK_END);   //Seek to very end of file
if(iReturn)                     //If can't Seek something is wrong.  Return err
    return iReturn;              //Return -1 (error code)
iLocation=ftell(fp);            //Get byte position of EOF in iLocation
fseek(fp,iTmp,SEEK_SET);        //Seek back to where caller was at held in iTmp

return iLocation/iRecLength;    //return total bytes divided by record length
}


long Put(FILE* fp, long iRecordNumber, void* pUdt, long iSize)
{
long iReturn=0;

if(iRecordNumber)
{
    if(fseek(fp,(iRecordNumber-1)*iSize,SEEK_SET))
       return false;
    iReturn=fwrite(pUdt,iSize,1,fp);
    if(iReturn==1)
       return true;
}

return false;
}


long Get(FILE* fp, long iRecordNumber, void* pUdt, long iSize)
{
long iReturn=0;

if(iRecordNumber)
{
    if(fseek(fp,(iRecordNumber-1)*iSize,SEEK_SET))
       return false;
    iReturn=fread(pUdt,iSize,1,fp);
    if(iReturn==1)
       return true;
}

return false;
}


int main(void)
{
long iRecCt=0;
FILE* fp=NULL;
Employee emp;

memset(&emp,0,sizeof(Employee)); //zero out emp record
fp=fopen("Data.dat","rb+");      //rb+ read/write binary; file must exist
if(fp)                           //test for NULL; If fopen() fails don't cont.
{
    puts("File Apparently Opened!  Lets First Read The 1st Record\n\n");
    if(Get(fp,1,&emp,sizeof(Employee)))
    {
       puts("Rec#    Name            Address                 City            State   Country ZipCode Age     EmpNum  Salary");
       puts("=================================================================================================================");
       printf
       (
         "1\t%s\t%s\t%s\t%s\t%s\t%s\t%u\t%u\t%6.2f\n\n",
         emp.szName,
         emp.szAddress,
         emp.szCity,
         emp.szState,
         emp.szCountry,
         emp.szZipCode,
         emp.iAge,
         emp.iEmpNum,
         emp.dblSalary
       );
    }
    else
       puts("Failed To Read First Record!");
   
    //Modify 1st record (Marty out - Eddie in)
    puts("\nNow Lets Put Somebody Else In The 1st (Zeroth) Record, Add Another");
    puts("Record At The End, And Dump The Output\n\n");
    memset(&emp,0,sizeof(Employee));
    strcpy(emp.szName,"Edward Kamara");         //add employee in zeroth place
    strcpy(emp.szAddress,"414 South Rock St.");
    strcpy(emp.szCity,"Shamokin");
    strcpy(emp.szState,"PA");
    strcpy(emp.szCountry,"USA");
    strcpy(emp.szZipCode,"17872");
    emp.iAge=16;
    emp.iEmpNum=789123;
    emp.dblSalary=10000;
    if(!Put(fp,1,&emp,sizeof(Employee))) //Put() returns false on failure so If
       puts("Failed To 'Put' Record!");  //will evaluate to true if we negate
    memset(&emp,0,sizeof(Employee));     //failure

    //Add a third record
    strcpy(emp.szName,"Daniel Higgens");         //add employee in record
    strcpy(emp.szAddress,"126 North Pearl St."); //number 3
    strcpy(emp.szCity,"Shamokin");
    strcpy(emp.szState,"PA");
    strcpy(emp.szCountry,"USA");
    strcpy(emp.szZipCode,"17872");
    emp.iAge=39;
    emp.iEmpNum=192837;
    emp.dblSalary=80000;
    if(!Put(fp,3,&emp,sizeof(Employee)))
       puts("Failed To 'Put' Record!");
    memset(&emp,0,sizeof(Employee));

    //Dump data as it now exists, i.e., altered first record and added third
    iRecCt=RecordCount(fp,sizeof(Employee));
    puts("Rec#    Name            Address                 City            State   Country ZipCode Age     EmpNum  Salary");
    puts("=================================================================================================================");
    for(unsigned int i=1; i<=iRecCt; i++)
    {
        if(Get(fp,i,&emp,sizeof(Employee)))
        {
           printf
           (
             "%u\t%s\t%s\t%s\t%s\t%s\t%s\t%u\t%u\t%6.2f\n",
             i,
             emp.szName,
             emp.szAddress,
             emp.szCity,
             emp.szState,
             emp.szCountry,
             emp.szZipCode,
             emp.iAge,
             emp.iEmpNum,
             emp.dblSalary
           );
        }
    }
    fclose(fp);
}
else
    puts("Couldn't Open File!");
getchar();

return 0;
}

/*
File Apparently Opened!  Lets First Read The 1st Record


Rec#    Name            Address                 City            State   Country ZipCode Age     EmpNum  Salary
=================================================================================================================
1       Martin Veneski  1016 Wabash Street      Shamokin        PA      USA     17872   23      123456  45000.00


Now Lets Put Somebody Else In The 1st (Zeroth) Record, Add Another
Record At The End, And Dump The Output


Rec#    Name            Address                 City            State   Country ZipCode Age     EmpNum  Salary
=================================================================================================================
1       Edward Kamara   414 South Rock St.      Shamokin        PA      USA     17872   16      789123  10000.00
2       Ray Latchkoski  1624 Tioga Street       Shamokin        PA      USA     17872   25      789123  50000.00
3       Daniel Higgens  126 North Pearl St.     Shamokin        PA      USA     17872   39      192837  80000.00
*/