Mark W. Schumann lives "Over The Bridge" in Cleveland's Archwood-Denison neighborhood, holds a Bachelor of Arts degree in Mathematics from Grinnell College in Grinnell, Iowa, and is a programmer/analyst with STR, Inc. He can be reached on Usenet at mark@wariat.ncoast.org; Compuserve 73750,3527; or 216-351-1153. Mark is not related to Jan and Kim Schumann, the principals of SoftC, Ltd.
The SoftC Database Library v3.0 is a flexible and inexpensive solution for C programmers who need to read and write xBase-compatible files in their applications. As a bonus it includes functions for date and time manipulation. MS-DOS compilers supported include Borland, Microsoft, and Zortech.
The SoftC DBL has been ported to MS-DOS, OS/2, XENIX, and other UNIX variants. SoftC does not promise extensive support for non-DOS environments but does claim that customers are currently using their product across those platforms. The libraries I purchased (Borland version) work without a hitch in Windows 3.0 applications. A Windows DLL version of the library is available, either free with a source code license or purchased separately.
My object was compatibility with existing Clipper applications. SoftC, however, claims also to handle dBASE III+, dBASE IV, and FoxBase index files correctly.
I've developed utilities and applications with the SoftC Database Library for about a year, and would like to share some of my experiences with you.
How It Works
I approached the SoftC DBL as a C programmer working in a Clipper shop, looking for a more efficient way to adapt small utility programs for in-house use and for our customers. It integrates well with any Clipper-type data files, although because of Clipper's unique symbol table and calling conventions you cannot use SoftC DBL functions within a Clipper program.SoftC's philosophy is consistent with the C outlook that more is less. There is no one-to-one correspondence between lines of dBASE or Clipper code, as you would expect from a true Clipper emulator. The SoftC DBL is not a function library trying to look like a fourth-generation language, nor is it a replacement for Clipper (or, presumably, your own favorite xBase dialect). It looks, feels, and smells like C code. Essentially you are back to the old tradeoff of C versus higher-level languages; you will put in a fair amount of extra work in return for much tighter and possibly faster code.
As an example, there is no SoftC function that replicates the functionality of the dBASE USE statement. Instead, you would step through this process:
1. Open the DBF file with scddopenx and return a database handle in the handle parameter. You can open the file with shared or exclusive access, and read/write or read-only usage. You can determine whether or not to buffer groups of records with a scddopenx parameter, and adjust the size of that buffer with scddbfrsz.
2. For each index file you wish to use, invoke the proper function of the scd?openx family. For Clipper indexes use scdcopenx; for FoxBase use scdiopenx; and for dBASE use scdnopenx. Each one of these also returns an index file handle in the handle parameter. Buffering options are similar to those for DBF files.
3. Now invoke scddrinfo to determine how many fields are in the database and how long your I/O buffer will have to be. (SoftC DBL will handle the I/O buffer, if you want.)
4. Given the number of fields in the database, you now malloc or (in C++) new an array of that many SC_FIELDINFO. One call to scddfinfo fills that array with field descriptions including field name, type, length, and decimal positions.
5. If you want to use any fields of memo type, you have to open the memo files separately with functions for that purpose. On the other hand, if you aren't interested in any memo fields that may exist, you can safely ignore them.
Reading a database record into the SoftC DBL internal buffers requires only one call to scddrget, but that doesn't load individual fields without use of the scddfget or scddfgets functions. Nor does it get the correct record number in an index situation without a call to scd?knext family of functions, which like scd?openx correspond to the dBase dialect with which you want to conform.
The advantage to this minimalist approach is that the SoftC DBL does only the work you need to get the job done. If you are interested in the values of only one or two fields in a database containing over 100 fields you can see the utility of this kind of low-level organization. Clipper programmers in particular will love seeing useful work done in executables less than 50K in size.
Error Handling
The SoftC DBL handles errors neatly and immediately. Every function's return code indicates success or failure with a specific error value, and a subsequent call to the function scecode returns the same value. scecode is positive for warning messages, negative for serious errors, and zero for simple success. Whenever a serious error occurs, all subsequent SoftC DBL functions fail, leaving the original error code intact, until you execute the sceclr function. Therefore, it pays to execute sceclr once at the top of every one of your own functions unless you want to get some misleading error codes.These two code fragments accomplish the same thing:
/*---- one way ----*/ scddfgets (handle, recno); if (scecode()) printf ("Error!\n"); /*---- another way ----*/ if (scddfgets (handle, recno) != SC_SUCCESS) printf ("Error!\n");I found it a little bit clumsy to code for multiple possible errors in a single segment of code. It is easy to let every SoftC function call carry all the error-handling baggage, but that increases your code bulk substantially and generally clutters the source. I evolved into a style in which any non-trivial set of SoftC operations is preceded with a call to sceclr and ends with a check against scecode. As long as the set of operations is not too ambitious, any error codes returned can be acted upon after the fact.For example, you might want to load a database record by index key and print the value of the first field. This code would do the job:
sceclr() scdckfind (ntx_handle, keyvalue, &recno, SC_EXACT); scddrget (dbf_handle, recno); scddfgets (dbf_handle, 0, buffer); if (scecode()) printf ("Error code was %d.\n", scecode()); else printf ("Field 1: %s\n", buffer);Since this code is self-contained, you can assume that any value of scecode came from one of the three function calls above the if statement. Any failure (negative return value) from one of function calls would stop the whole process until you check the error code and act on it. This technique saves a lot of work without taking unnecessary chances with program logic or data integrity.The function scemsg always returns a pointer to a string describing the problem associated with the current value of scecode. I have found it useful to write error-handling code like this:
if (scddrget (handle, recno) != SC_SUCCESS) { fprintf (stderr, "Couldn't open file %s!\n" "SoftC returns error (%d) %s\n", scecode(), scemsg()); }In Windows code you can do the same:
if (scddrget (handle, recno) != SC_SUCCESS) { MessageBox (hwnd, (LPSTR) scemsg(), (LPSTR) "SoftC Error", MB_OK); }This gives you (and possibly your end user) some kind of hint as to what went wrong without your having to hit the reference manual.
A Few Nice Touches
If you don't feel like writing your own progression of strcpy, strcat, and itoa calls, the SoftC DBL provides a handy scdckmake function that evaluates key expressions for you. The manual warns, however, about using this function lightly since it drags so much overhead into your application. It is there if you need it, though.The scddfgets and scddfputs functions differ from their relatives scddfget and scddfput (without the S) by automatically converting field values to and from strings. This makes data-dump or browsing utilities much simpler to write, as you can pretend that all fields are of character type when that is convenient for you.
I really like the way the SoftC DBL handles character-to-logical conversion. For one particular application I needed to do a type-safe replacement for the Clipper APPEND FROM command. Specifically, given two arbitrary dBASE files (with or without memos) I wanted to zap one and copy into it all records from the other. (Fields with matching names of course would copy over.) The problem with Clipper's APPEND FROM command is that if a field appears in both the source and destination files with different types you will get a type mismatch error.
One nice touch appeared when I converted part of a Clipper application that now uses all Y and N character fields where logicals should have been used. The SoftC scddfgets and scddfputs functions handled this without a hitch; they sensibly took a Y value to be equivalent to TRUE, and a N or blank value to be a FALSE. This is a detail that could have been left out in a true bare-metal implementation, but I definitely appreciated it. If it violates SoftC's minimalist spirit, I guess I can live with that.
There is a reasonably good suite of date and time manipulation functions such as scddi2s, which converts year, month, and day integer values into a character string matching the currently selected date format, and sccdl2dow, which returns the day of week for a given date parameter. These functions do validate dates and correctly interpret leap years.
A Slick Speed Shortcut
At one point I was experimenting with porting another application, this time an ASCII-to-DBF file conversion, to the SoftC DBL (see Listing 1) . The objective was to read a single flat ASCII file, looking for marker records indicating destination databases, and dropping other records into those destinations with no translation. Effectively this was like using the APPEND FROM ... TYPE SDF command in Clipper, except that the Clipper command wasn't smart enough to handle multiple destination files.I wanted to read whole records from the source stream, check and adjust for a data-marker record, and write the record (if not a data marker) to the currently open DBF file. The scddrputx function jumped right out; it writes a record directly from a user-defined memory buffer, without the necessity of loading individual fields. The catch, hinted at by example in the manual for v2.1 and explained explicitly in v3.0, is that the first byte of your buffer must be reserved for the blank record-status byte, which dBASE dialects use to indicate records flagged for deletion.
This code runs at least three times as fast as the ancestor Clipper function, which created two large, temporary files in order to take advantage of the higher-level APPEND FROM ... TYPE SDF command. (The original programmer on that project had considered but rejected an awkward parsing of the input line based on the target database's field structure, since Clipper provides no documented way to transfer a whole record at a time. Moral: Tools do indeed shape algorithms, and not always for the better.)
Recursion and Index Buffers
On another little program, this one a stripped-down calling tree list application (available on the code disk), I found some interesting implications of the low-level approach. In this application, the idea was to print the name and phone number in the current record at the given indentation level, then recursively print in the same way, at the next indentation level, all records called by it.When using recursion with the SoftC DBL, it is necessary to find a record in the index all over again to bring the index's internal buffers back to the same consistent state. Just re-reading the record with a scddrget call is not enough. An extra call to scdckfind is required for the following scdcknext call to point to an incorrect next record.
This is a gotcha that was obvious only when found, although in hindsight there is no reason to expect that reading a data file record should magically update an index file record pointer. With higher-level languages such as Clipper the details of positioning the index pointer are taken care of, but manipulating databases in C or C++ is the application programmer's responsibility.
Summary
If you are looking for a way to manipulate dBASE-type data and index files with minimum interference from your programming language, use the SoftC DBL with your favorite C compiler. If you are seeking an easy port of an existing Clipper application to C, perhaps you want to look elsewhere.If your development is for an audience with existing Clipper, dBASE, or FoxBase applications you will find most worthwhile the extra attention that was given to compatibility issues. Many libraries can handle the dBASE DBF data file format, but few actually correctly use the various index file formats that exist. The SoftC DBL scores high marks for compatibility, at least as far as I can verify with Clipper.
I found that writing Windows 3.0 code with the SoftC DBL presents no special problems. Since most commercial Windows development is still done in C or C++, it is the kind of environment where the SoftC DBL might be particularly useful.
As I've said, the SoftC DBL is not a replacement for Clipper or any other dBASE-derived language. It is still another C function library, using C data types and C syntax. If you are looking for a complete @ ... SAY ... GET interface or the slick Clipper functions like DBEDIT you will be disappointed. The SoftC DBL is not an easy tool for porting existing, nontrivial applications from Clipper to C if that is what you have in mind. On the other hand, if you are interested in the features that make C such a great development language such as tight executable code, freedom to manipulate underneath the database structures when you want to, portability to many environments, and C++ compatibility you may want to investigate the SoftC Database Library.
Product Information:
Product: SoftC Database Library
Price: $195.00
Soft C Ltd.
16820 3rd St. NE
Anoka, MN 55304
(612) 434-6968System Requirements:
MS-DOS 2.0 or later
MS-Windows 3.0 or later
UNIX/XENIX SVR4
OS/2 v1.1
6MB disk space