/*
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
*/
/*
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
*/