Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced C 1992

.pdf
Скачиваний:
96
Добавлен:
17.08.2013
Размер:
4.28 Mб
Скачать

Part III • Working with Others

A function written in C can be called from other languages. When a C function is called from an assembly program, library functions can be called provided the C startup code is incorporated into the assembly program. If the other language is not assembly, however, the C library functions cannot be used.

You must consider how each of the variable types, especially arrays, are handled between C and other languages. Do not exceed the bounds of an array. Arrays in FORTRAN are indexed starting with one; arrays in C are indexed starting with zero.

The FORTRAN compiler converts all external identifiers in FORTRAN programs to uppercase. C and assembly external identifiers can be mixed case.

Arguments for a C function are placed on the stack in the opposite order of arguments in FORTRAN or Pascal.

With most C compilers, it is easy to write the shell of an assembly function in C, compile it with an assembly listing option, and convert the assembly listing to an assembly file. This ensures that the parameters, calling protocol, and return values are correctly coded.

466

C Cand DatabasesCC C C

C12C C

C12C CC C C

C C C

C and Databases

Computers generally do two things: number management and data management. Most end users (nonprogrammers) manage data or numbers, and most of the software tools they use are related to these two tasks.

Number managementis usually performedusing a spreadsheet program. Spreadsheets are complex programs that enable a person to manipulate numeric data, generate what-if scenarios, create reports, and so on. Because spreadsheet programs have become standardized, there are a limited number of add-on products.

With data management systems, the story is different. Many custom add-on programs directly interact with the database program and with files created with the database program.

This chapter covers the interaction of C programs with database programs and the use of the data files created with database programs. When the name dBASE is mentioned, the information usually applies to most other compatible programs, such as FoxBase.

467

Part III • Working with Others

Products such as R:Base and Oracle are incompatible with dBASE. You can write callable C functions for use with R:Base, but the real power when using R:Base files comes from a library of functions (available from Microrim, which supplies R:Base) that allow C programs to access R:Base database files using the same command functionality as the R:Base interactive commands.

Interfacing with dBASE-Compatible Programs

The primary way to interface with database programs is with Clipper, a dBASEcompatible compiler (just like a C compiler). Clipper is not a C compiler, however, so some of the calling conventions are different.

For example, a C function called from a Clipper program (Summer 1987 version) does not receive its arguments directly. Clipper places the arguments in external variables, and the called function must make assignments from these external variables.

Because there are no interfacing standards, the procedures for interfacing C with a database program differ depending on the product. Knowing how to interface C with dBASE, for example, will not help you interface C with Clipper, FoxBase, or Paradox.

Using dBASE Files Directly

Rather than using Clipper, dBASE, or another database program to access a dBASE file, it is more common to include in a program the support to directly access a dBASE and dBASE-compatible file.

There are three main versions of dBASE that you must work with. The dBASE II program is old and rarely used. dBASE III is more common, is still used extensively, and is the standard that other programs follow when creating a dBASEcompatible file. The dBASE IV program is the latest of the dBASE programs that you might encounter. This chapter covers dBASE III and dBASE IV’s file features that are compatible with dBASE III.

Accessing a file created by dBASE is as easy as accessing any other file. The format of a simple dBASE file is shown in Figure 12.1. This figure shows the layout of the file header, the column headers, and the data.

468

C and Databases

C C C

 

12C

 

C C C

 

C C

Figure 12.1. A dBASE file.

This chapter does not describe the special features of dBASE IV or how to access indexes. By the end of this chapter, however, you should be able to read a dBASE file and create a file that dBASE can read.

The dBASE file format is simple. Using a program such as DUMP, you could easily dump a simple dBASE file and determine the use of each file variable. So that you do not have to do this, however, this section describes the two structures that make up the file’s header.

The first structure (called DB3HEADER in Listing 12.1) defines the file header and describes the dBASE file. This information enables you to determine the file’s contents, such as the file’s record layout, the number of records in the file, the date the file was updated, and the version of dBASE that created the file.

#pragma pack 1 /* Pack to byte boundaries */ typedef struct {

/* Bit fields are arranged from least significant to most significant */

unsigned

int

bfVersion:7;

unsigned

int

bfHasMemo:1;

unsigned

int

bYear:8;

unsigned

char

bMonth;

unsigned

char

bDay;

long int

 

lNumberRecords;

short int

 

nFirstRecordOffset;

short int

 

nRecordLength;

469

Part III • Working with Others

unsigned char szReserved[20]; } DB3HEADER;

The main header structure has a series of bit field variables. This allows a direct test of the memo field bit, which is otherwise difficult to access, as follows:

unsigned short int bfVersion:7;

The bfVersion variable is a 7-bit bit field. Its value designates the version of dBASE that created the database file. For most dBASE-compatible files, bfVERSION contains one of the values in Table 12.1.

Table 12.1. Version codes for dBASE-compatible files.

File type

Description

0x02

0x03

dBASE II file. Generally incompatible with dBASE III.

dBASE III or FoxBase file. The file could have been created with dBASE IV, but the file is compatible with dBASE III.

0x04

dBASE IV file, partially compatible with dBASE III file I/O

 

routines.

0x75

FoxBase file. The memo file bit is usually set (see discussion

 

of bfHasMemo).

0x0B

dBASE IV file. Memo file bit is usually set (see discussion of

 

bfHasMemo).

 

 

Generally, a value of 3 in the version field (bfversion) means that the file is compatible with dBASE III. If the bfHasMemo variable (a single bit) is True, a .DBT memo file is included:

unsigned short int bfHasMemo:1;

The functions in this chapter do not access memo files, so this bit and any memo fields can be ignored.

ANSI standards require a bit field to be defined as a short int, so the bYear bytesized variable (the year that the database was last updated) is included as the final eight bits in the bit field:

unsigned short int bYear:8;

470

C and Databases

C C C

 

12C

 

C C C

 

C C

This prevents us from taking the address of this variable, but we never need its address. (If we needed the address of bYear, we could define a union to map a variable to this address.)

The bMonth byte variable contains the month the database was last updated:

unsigned char bMonth;

The bDay byte variable contains the day the database was last updated:

unsigned char bDay;

The lNumberRecords variable contains the number of database records in the database. The program uses this value to determine how many records must be read.

long int

lNumberRecords;

The number of fields in each record and the location of the first record is computed with the nFirstRecordOffset variable:

unsigned short int

nFirstRecordOffset;

Because a file seek operation requires a long type, you must cast this variable to a long. A short int is used because a dBASE header is never more than 64K.

The nRecordLength variable holds the length of each record in the database:

short int

nRecordLength;

The first byte of each record is a flag field. This flag field contains a blank if the record is not deleted, or * if it has been deleted.

The reserved fields should not be used or modified. Generally, they contain

zeros:

unsigned char szReserved[20];

After the header is read, the column definition records (often called field definition records) can be read. There is one column definition record for each column in the database.

You usually do not know how many columns will be defined in a database when you write your program (and please do not guess a “really large number”). Therefore, the program must compute the number of column definitions by reading the information in the file header.

471

Part III • Working with Others

Each column definition is contained in a structure as shown:

typedef struct

{

char

szColumnName[11];

char

chType;

long

lFieldPointer;

unsigned char

byLength;

unsigned char

byDecimalPlace;

char

szReserved[14];

} COLUMNDEF;

 

The first field, szColumnName, contains the name of the column as a standard C string (terminated with \0). This name may be up to 10 characters long, leaving a final 11th byte to hold the terminating \0.

char

szColumnName[11];

 

The column type is coded in the single character member called chType:

char

chType;

This code can contain any of the characters shown in Table 12.2. The field definition characters in Table 12.2 are valid for dBASE III. For other versions of dBASE, other fields may be defined.

Table 12.2. dBASE column definition characters.

Value

Identifier

Description

N NUMERIC_FIELD

The field is a number, either integer (if the decimal places are 0) or floating point.

C

CHARACTER_FIELD

The field is a character string.

L

LOGICAL_FIELD

The field is logical, containing Y, y, N, n,

 

 

T, t, F, or f

M MEMO_FIELD

The field is a pointer to a 512-byte memo field in the .DBT file, so the file position is computed as 512 times the field’s value. A memo field has a fixed length of 512 bytes.

D DATE_FIELD

The field is a date formatted as YYYYMMDD.

472

C and Databases

C C C

 

12C

 

C C C

 

C C

Value

Identifier

Description

F FLOAT_FIELD

The field is a floating-point number. (Not found in all database programs because this type is not dBASE III-compatible).

P PICTURE_FIELD

The field is in picture format. (Not found in all database programs because this type is not dBASE III-compatible.)

The lFieldPointer member is used by some versions of dBASE as the displacement of the field in the record:

long lFieldPointer;

Because you should not depend on this field being set, the field offsets are computed based on the sizes of the previous fields. For most versions of dBASE, lFieldPointer is initialized to zero. Many programs that create a dBASE-compatible file use this field for their own purposes—whether this is a good idea is up to you, the programmer.

The byLength member contains the length of the field:

unsigned char byLength;

This length is the only indicator of where one field ends and the other begins. The sum of byLength for each column, plus 1 for the record status byte, equals the file header’s nRecordLength member. (The record status byte is also called the deleted flag byte and is the first byte in every record.)

The program uses the byDecimalPlace member to determine the format of the numbers in the column. If the value of byDecimalPlace is zero, the numbers in the column are integer. If the value is greater than zero, the numbers are floating point. Because decimal places are stored in the database, you can use simple scanf() calls to read the column’s value. If necessary, the column’s value can be saved for later display of the number.

unsigned char byDecimalPlace;

Finally, 14 bytes of space in the column definition are unused and marked as reserved. You should not use these bytes:

char szReserved[14];

473

Part III • Working with Others

Reading dBASE and dBASE-Compatible Files

Reading dBASE-compatible files is a simple process. These files consist of a file header, column headers, and data. They are organized in a fixed manner, have a simple structure, and have data that is generally in an ASCII format, easily read into a set of variables using sscanf() function calls.

To reada dBASE file, you needonly a simpleprogram. Listing 12.1, DBREAD.C, reads a dBASE III file and prints each record’s raw data to the screen.

Listing 12.1. DBREAD.C.

/* DBREAD, written 1992 by Peter D. Hipson */ /* This program reads dBASE III files. /*

#include <stdio.h> #include <stddef.h> #include <stdlib.h>

/* Some defines useful for dBASE files: */

/* Actual record status defines (first byte of each record) */ #define DELETED_RECORD ‘*’

#define USABLE_RECORD ‘ ‘

/* Field (column) definitions (capital letters, please) */

#define NUMERIC_FIELD

‘N’

#define CHARACTER_FIELD

‘C’

#define LOGICAL_FIELD

‘L’

#define MEMO_FIELD

‘M’

#define DATE_FIELD

‘D’

#define FLOAT_FIELD

‘F’

#define PICTURE_FIELD

‘P’

/* End of dBASE defines */

#pragma pack(1) /* Pack to byte boundaries */

typedef struct {

474

 

 

 

C and Databases

C C C

 

 

 

 

12C

 

 

 

 

C C C

 

 

 

 

C C

/*

Bitfields are arranged from least significant to most significant */

 

unsigned

int

bfVersion:7;

 

 

unsigned

int

bfHasMemo:1;

 

 

unsigned

int

bYear:8;

 

 

unsigned

char

bMonth;

 

 

unsigned

char

bDay;

 

 

long int

 

lNumberRecords;

 

 

short int

 

nFirstRecordOffset;

 

 

short int

 

nRecordLength;

 

 

unsigned

char

szReserved[20];

 

 

} DB3HEADER;

 

 

typedef struct

{

 

 

char

 

szColumnName[11];

 

 

char

 

chType;

 

 

long

 

lFieldPointer;

 

 

unsigned

char

byLength;

 

 

unsigned

char

byDecimalPlace;

 

 

char

 

szReserved[14];

 

 

} COLUMNDEF;

 

 

int

main()

 

 

 

{

 

 

 

 

FILE *DBFile;

DB3HEADER db3Header;

COLUMNDEF *ColumnDef;

unsigned char * pBuffer;

char

szFileName[25];

int

i;

int

nColumnCount = 0;

int

nResult;

continues

475