Part III • Working with Others
Listing 12.2. continued
int main()
{
FILE *DBFile;
DB3HEADER |
db3Header; |
struct _DBRECORD { |
char |
sStatus[1]; /* Status does not count as a member */ |
char |
sName[40]; |
char |
sAddr1[40]; |
char |
sAddr2[40]; |
char |
sCity[20]; |
char |
sState[2]; |
char |
sZip[5]; |
} OurRecord; |
char |
*pOurRecord; |
COLUMNDEF |
ColumnDef[6]; /* Six members in OurRecord */ |
char |
szBuffer[200]; /* Work buffer */ |
char |
szTime[26]; |
char |
szFileName[25]; |
int |
i; |
int |
nColumnCount = 0; |
int |
nResult; |
long |
lCurrentRecord = 0; |
double |
dSales = 0.0; /* Forces loading of floating-point support */ |
struct tm |
*NewTime; |
time_t |
aClock; |
/* Step 1. Determine the layout of the columns (fields). In this * example, they are predefined. In other programs, you
C and Databases |
C C C |
|
12C |
|
C C C |
|
C C |
*might determine the column layout by prompting the user
*or by examining the user’s data.
*/
printf(“Please enter new database name: “);
gets(szFileName);
DBFile = fopen(szFileName, “wb”);
if (DBFile == NULL)
{
fprintf(stderr,
“ERROR: File ‘%s’ couldn’t be opened.\n”, szFileName);
exit(4);
}
/* Step 2. Initialize and write the header record. */
time( &aClock );
NewTime = localtime(&aClock);
memset(&db3Header, 0, sizeof(db3Header));
/* Make it dBASE III-compatible */ db3Header.bfVersion = 3;
/* Make it a database with no memo fields */ db3Header.bfHasMemo = 0;
|
|
|
/* Set the date to |
now, but UPDATE when closing */ |
/* because date may have changed */ |
db3Header.bYear = NewTime->tm_year; |
db3Header.bMonth |
= (unsigned char)(NewTime->tm_mon + 1); |
db3Header.bDay |
= (unsigned char)NewTime->tm_mday; |
/* No records in the database yet */ db3Header.lNumberRecords = 0;
continues
Part III • Working with Others
Listing 12.2. continued
/* File header, plus column headers, plus a byte for the carriage return */
db3Header.nFirstRecordOffset = sizeof(DB3HEADER) + sizeof(ColumnDef) + 2;
/* Make it the size of a record in the database */ db3Header.nRecordLength = sizeof(OurRecord);
nResult = fwrite((char *)&db3Header, sizeof(DB3HEADER),
1,
DBFile);
if (nResult != 1)
{
fprintf(stderr, “ERROR: File ‘%s’, write error (Database \ header).\n”,
szFileName);
fclose(DBFile);
exit(4);
}
/* Step 3. Initialize the column headers using the information from step 1.*/
memset(ColumnDef, 0, sizeof(ColumnDef));
/* Do the following for each column, either inline or using a loop */
strcpy(ColumnDef[0].szColumnName, “Name”); ColumnDef[0].chType = CHARACTER_FIELD; ColumnDef[0].byLength = sizeof(OurRecord.sName); ColumnDef[0].byDecimalPlace = 0;
strcpy(ColumnDef[1].szColumnName, “Address1”); ColumnDef[1].chType = CHARACTER_FIELD; ColumnDef[1].byLength = sizeof(OurRecord.sAddr1);
C and Databases |
C C C |
|
12C |
|
C C C |
|
C C |
ColumnDef[1].byDecimalPlace = 0;
strcpy(ColumnDef[2].szColumnName, “Address2”); ColumnDef[2].chType = CHARACTER_FIELD; ColumnDef[2].byLength = sizeof(OurRecord.sAddr2); ColumnDef[2].byDecimalPlace = 0;
strcpy(ColumnDef[3].szColumnName, “City”); ColumnDef[3].chType = CHARACTER_FIELD; ColumnDef[3].byLength = sizeof(OurRecord.sCity); ColumnDef[3].byDecimalPlace = 0;
strcpy(ColumnDef[4].szColumnName, “State”); ColumnDef[4].chType = CHARACTER_FIELD; ColumnDef[4].byLength = sizeof(OurRecord.sState); ColumnDef[4].byDecimalPlace = 0;
strcpy(ColumnDef[5].szColumnName, “Zipcode”); ColumnDef[5].chType = CHARACTER_FIELD; ColumnDef[5].byLength = sizeof(OurRecord.sZip); ColumnDef[5].byDecimalPlace = 0;
nResult = fwrite((char *)ColumnDef, sizeof(ColumnDef),
1,
DBFile);
if (nResult != 1)
{
fprintf(stderr, “ERROR: File ‘%s’, write error (Column \ headers).\n”,
szFileName);
fclose(DBFile);
exit(4);
}
/* Step 4. Write a carriage return (and a NULL) to meet dBASE standards */
continues
Part III • Working with Others
Listing 12.2. continued
nResult = fwrite((char *)”\x0D\0", sizeof(char) * 2,
1,
DBFile);
if (nResult != 1)
{
fprintf(stderr, “ERROR: File ‘%s’, write error (Column \ headers).\n”,
szFileName);
fclose(DBFile);
exit(4);
}
db3Header.nFirstRecordOffset = (int)ftell(DBFile);
/* Step 5. Get some records for the database. */
memset(&OurRecord, 0, sizeof(OurRecord));
printf(“Enter the name: (or no name to end)”); gets(szBuffer);
strncpy(OurRecord.sName, szBuffer, sizeof(OurRecord.sName));
while (strlen(szBuffer) > 0)
{
OurRecord.sStatus[0] = USABLE_RECORD;
printf(“Enter address line 1: “); gets(szBuffer);
strncpy(OurRecord.sAddr1, szBuffer, sizeof(OurRecord.sAddr1));
printf(“Enter address line 2:”); gets(szBuffer);
strncpy(OurRecord.sAddr2, szBuffer, sizeof(OurRecord.sAddr2));
printf(“Enter city:”);
|
C and Databases |
C C C |
|
|
12C |
|
|
C C C |
|
gets(szBuffer); |
C C |
|
|
|
strncpy(OurRecord.sCity, szBuffer, sizeof(OurRecord.sCity)); |
|
|
printf(“Enter state (2 characters only):”); |
|
|
gets(szBuffer); |
|
|
strncpy(OurRecord.sState, szBuffer, sizeof(OurRecord.sState)); |
|
printf(“Enter Zipcode:”); |
|
|
gets(szBuffer); |
|
|
strncpy(OurRecord.sZip, szBuffer, sizeof(OurRecord.sZip)); |
|
/* |
dBASE records do not contain NULLs, but are padded with |
|
* |
blanks instead, so we convert the NULLs to spaces |
|
*/
pOurRecord = (char *)&OurRecord;
for (i = 0; i < sizeof(OurRecord); i++)
{
if (pOurRecord[i] == ‘\0’)
{
pOurRecord[i] = ‘ ‘;
}
}
nResult = fwrite((char *)&OurRecord, sizeof(OurRecord),
1,
DBFile);
if (nResult != 1)
{
fprintf(stderr, “ERROR: File ‘%s’, write error (Column \ headers).\n”,
szFileName);
fclose(DBFile);
exit(4);
}
else
{
continues
Part III • Working with Others
Listing 12.2. continued
++db3Header.lNumberRecords;
}
memset(&OurRecord, 0, sizeof(OurRecord));
printf(“Enter the name: (or no name to end)”); gets(szBuffer);
strncpy(OurRecord.sName, szBuffer, sizeof(OurRecord.sName));
}
/* Step 6. Update the file header with the current time and * the number of records.
*/
time( &aClock );
NewTime = localtime(&aClock);
|
|
|
/* Set the date to |
now */ |
db3Header.bYear = NewTime->tm_year; |
db3Header.bMonth |
= (unsigned char)(NewTime->tm_mon + 1); |
db3Header.bDay |
= (unsigned char)NewTime->tm_mday; |
/* The number of |
records is already set */ |
nResult = fseek(DBFile, (long)0, SEEK_SET);
if (nResult != 0)
{
fprintf(stderr, “ERROR: File ‘%s’, seek error (rewrite of \ header).\n”,
szFileName);
fclose(DBFile);
exit(4);
}
nResult = fwrite((char *)&db3Header, sizeof(DB3HEADER),
C and Databases |
C C C |
|
12C |
|
C C C |
|
C C |
1,
DBFile);
if (nResult != 1)
{
fprintf(stderr, “ERROR: File ‘%s’, write error (Database header \ rewrite).\n”,
szFileName);
fclose(DBFile);
exit(4);
}
/* Finished. Close the file and end the program. */
fclose(DBFile);
return(0);
}
Your program that creates a dBASE file must do the following, in order:
1.Determine the record layout. This may have been already defined by the application’s requirements, or you may be using information supplied by the user. Each field must have a type and a length.
2.Initialize and write a dBASE file header (our DB3READ structure). You must initialize the record size (sum of fields plus the leading record deleted byte), the pointer to the first record, usually the byte after the end of header byte (see step 4), the date, and the time.
3.Initialize and write the field headers (our COLUMNDEF structure). Be sure you specify the field’s name, length, and type.
4.Write the end of the header byte (0x0D); following the header byte with a NULL byte (0x00) is optional.
5.Write the records placed in the file at creation time. Be sure the deleted flag is set to blank so that the initial records are not deleted.
Part III • Working with Others
6.Seek to the beginning of the file and reset the file header’s number of records count, date, and time. (The date and time are reset because they have changed since they were last written.) Rewrite the file header.
Each of these six steps were followed in DBWRITE.C and are indicated by comments in the program.
Updating dBASE and dBASE-Compatible Files
When you update a dBASE file, I suggest that you update a copy of the original file. If you update the original file, you risk damaging the original file if your program crashes. I never work on the only copy of a file. Instead, I copy the original .DBF file to a .BAK file or a temporary work file.
The steps for updating a dBASE file follow:
1.Read the file header and the column (field) header records.
2.Read the file’s data records if necessary. Append to the file any new records that the user creates. If the user is deleting a record, mark it with the * delete flag in the first column (the deleted field) of the data record. Modify records as necessary. When modifying a record, many programs mark the original record as deleted, then make the changed record a new record. Although this technique is acceptable, the database file may grow excessively if many records are changed.
3.Write the updated file header record with a new date and record count.
Using a dBASE-compatible file structure increases your program’s flexibility because the user can use other utilities (including a database program) to manipulate the data files.
Summary
In this chapter, you learned about interfacing C with database programs and with files created by dBASE-compatible programs.
•Database programs are one of the most common computer applications.
•The most common database file format is the dBASE III format. It is simple and easy to use.
C and Databases |
C C C |
|
12C |
|
C C C |
|
C C |
•A dBASE III .DBF file can be read using two structures, as shown in the chapter.
•A dBASE III file can be created using simple C functions.
•A .DBF file can contain deleted records. It is possible to write a program that recreates the database and undeletes the deleted records—if the database has not been packed and the space that the deleted records occupied has not been lost.