
This article is reprinted from the February 1991 edition of TechNotes/dBASE
IV.  Due to the limitations of this media, certain graphic elements such as
screen shots, illustrations and some tables have been omitted.  Where
possible, reference to such items has been deleted.  As a result,
continuity may be compromised.  

TechNotes is a monthly publication from the Ashton-Tate Software Support
Center.  For subscription information, call 800-545-9364.

Easy .BINs in C
Roland Bouchereau


Using other languages to make .BIN files for dBASE IV is getting easier
every day.  But their making is not without a set of special rules.  The
focus of what I will be presenting here shows the ins and outs of
constructing dBASE IV .BIN files written in the C programming language;
specifically, version 2.0 of Borland's Turbo C.  Fair warning:  This is not
for the inexperienced, uninitiated, squeamish, small children or those
wearing pacemakers.  So, with that said, let's jump right in.

The number of third party .BIN file products available to users of dBASE
III Plus were many.  The core of the dBASE Programmer's Utilities, Volume
II was a set of .BIN files written in Lattice C, then a popular C
compiler.  The author of the utilites, Ralph Davis, chose Lattice C because
of two major points.  The Lattice C compiler "provides an option for
creating .COM files, which are closely akin to .BIN files."  Secondly, the
assembler source code for the initialization or start-up routines is
supplied with the compiler.  The approach the author used was to rewrite or
replace the start-up code for use within the dBASE environment.  A very
similar approach is taken in chapter twenty of the book dBASE Power,
written by P. L. Olympia, R. Russell Freeland and Randy Wallin.  The
compiler used is version 1.5 of the Turbo C compiler, however.  The
guidelines and routines in this text reflect use of version 2.0 of the
Turbo C compiler, but much of their discussion relates directly to the
methods employed here.  The authors provide good arguments for using Turbo
C, not least important is the fact that the start-up code is fairly clear
and straightfoward.  This makes it easier to modify it to our needs, if
necessary.

Part of our goal here will be to eliminate the need for the assembly
written start-up code.  This will simplify the creation of .BINs as well as
remove the need for an assembler, a negative handicap for some of us.  A
linker and the DOS EXE2BIN utility will still be necessary, however.  When
replacing the start-up code, we must, as best possible, provide the
compiled program an environment most conducive to its operation.  The
start-up code presented by the authors of dBASE Power takes care of a few
key tasks necessary to prepare just such an environment.  An excerpt of
that code is listed below.              

This start-up code handles four important tasks.  

*               Aligns the code segment with DGROUP.  This is done in the group and
assume directives and is necessary if we are to reference any data local to
the .BIN.  
*               Asserts the local data segment.  Also necessary if we are to use
local data.  
*               Calls the main program main(), and 
*               executes a far return back to dBASE control.  The dBASE parameter
referencing is useful but optional and may be handled in other ways as
we'll see later.

Memory Model and Segment Placement

The two factors that most greatly determine how a .BIN file is created are
memory model and segment placement.  These issues are actually one and the
same since the compiler makes different assumptions on segment placement
depending on the memory model.  Other issues are what registers to save, if
any, parameter addressing, whether it is necessary to implement a stack,
what global variables need to be declared, and what library routines may be
used without undermining the rest of the program code.

Let's tackle the issue of memory model. Since only routines within the .BIN
file may be called, only models supporting near code can be used.  EXE2BIN
will not allow us to convert the .EXE file to a .BIN if there exist
references to any far routines called from our code.  That leaves us with
the tiny, small and compact memory models.  Our main routine, however, must
use a far return when returning back to dBASE and thus must itself be a far
routine, regardless of model.

Once the desired model is chosen, some Turbo C extensions allow us to
handle some of the interface in the C program itself as the code below
(Reverse.C) illustrates.

This works like a charm.  The MK_FP macro combined with the ability to
directly access CPU registers are what really get this code working.  The
routine returns to dBASE with a far return since we declared main() to be
far, and our pointer to the dBASE parameter is automatically far since we
are using the compact memory model.  We need little more than this, if our
.BIN files don't have to be any more complex.  But if our .BIN files are to
be more complex or use many library routines, then we must find a means of
properly referencing the data segment within our .BIN.  This involves two
separate and seemingly disparate steps.  The code shown on the next page
(Sample.C) follows suit with the techniques employed so far and employs
techniques for accessing data local to the .BIN file.

Registers, Linkers, and Switches. Oh My!

Equating the ds register to be the same value as the cs register tells the
CPU where to find our data.  This is only half of the trick though.  The
other part involves giving the linker the same kind of instruction, that
is, telling it that the offsets to our data should be relative to code
segment.  Oddly enough this does not involve any linker parameters but a
compiler switch.  The -z directive allows us to change or specify names for
the default segments, groups and classes for the Turbo C compiled code. 
For example, the  start up code example would have to be compiled using the
following command line.

tcc -c -mc -zPDGROUP sample

The -c tells the compiler to compile only, do not link the main program
with the standard Turbo C start-up code.  The -mc switch indicates that we
are compiling to the compact memory model.  The last switch is what
accomplishes the other part of data alignment technique.  It tells the
compiler to align the code segment as a part of DGROUP, the same group used
for data segments.  I cannot stress enough the importance of using this
switch when making .BINs.  It should be used even if you don't include any
data items yourself; the compiler and linker may do so on their own.  The
sample code fragment above may be used quite reliably as a template for the
structure of your C-coded .BIN.  

Now that all that's been said, here's a point-by-point guideline for making
.BINs.

*               dBASE IV requires that your code, data and stack (should you decide
to implement one) together not exceed 32K.
*               The source program must be compiled using a near code memory model,
that is, either the tiny, small, or compact model.
*               All code in the .BIN should be linked in at the lowest memory
address.  This will be handled properly if the -zPDGROUP command line
switch is used.
*               The need to statically initialize global data is imperative.  This
is because Turbo C puts uninitialized global data in the  BSS data
segment.  This data segment will effectively not exist by the time your C
program is a .BIN, and will cause memory outside your .BIN to be
overwritten.
*               Always compile with the -c option.  This will prevent the automatic
invocation of the linker, which attempts to link in the normal start-up
code for building .EXE and .COM files.
*               The program should be compiled with stack checking off.  The stack
checking routine assumes that you have linked in the normal start-up code. 
Stack checking is off by default.
*               The main() routine must be the first function in the source and
must be declared explicitly as far.  This is so that the .BIN returns
control back to dBASE IV using a far return.
*               When manipulating dBASE parameters, you are free to change the
content of the parameters, but their lengths must not be altered.
*               If a stack must be used, you must manipulate the _SS and _SP
variables to point to a local array.  Be sure to save their original values
and restore them before returning to dBASE IV.
*               Unless the compact memory model is used, dBASE parameter strings
can not be accessed without identifying them explicitly as far.  Library
functions only work on data addressed in their specific memory model. 
Therefore you must write functions to copy or manipulate the parameters
yourself.
*               Long integer arithmetic, either signed or unsigned, can only be
used in the tiny memory model.  This is because long arithmetic operations
are not compiled to inline operations but to a call to a far routine.  The
compiler and linker together perform some low-level sleight of hand to
accomplish this.
*               No memory allocation routines may be used.  This is a general no-no
when constructing any dBASE IV .BIN file.
*               No floating point arithmetic can be used.  This is because the
floating point library breaks many of the rules we've already laid down and
because it takes up too much code space and because I can't come up with a
trick to make it work anyway!

Even with all these rules to follow, making .BINs for dBASE IV should be
quite easy.  Here is the source code for three .BIN files.  They should
serve as excellent examples for writing your own .BINs and be quite useful
as well.  In addition, there is a library of UDFs at the end of the code
lisitngs (DD.PRG) to help you make better use of the C routines herein. 

--------------------------------------------------------------------------
REVERSE!.C 
/* 
                reverse!.c  Reverses as many parameters as sent to it! 

                Build reverse!.BIN as follows:

                tcc -c -mc -zPDGROUP reverse!
                tlink reverse!,,,cc
                exe2bin reverse!
*/

#include <string.h>
#include <dos.h>

int                      argc = 0;
char                    ** argv = (char **)0L;

far main()
{
                 DS = CS;  argc =  CX;  argv = MK FP( ES, DI);

                while ( argc + 1)
                        strrev( argv[ argc]);
}

----------------------------------------------------------------------------
SCROLL.C
/* 
                scroll.c  Makes SCROLL.BIN.  Use it as follows:

                                . CALL Scroll WITH 10,10,20,54,-3,"/BG"
*/
#include <dos.h>

#define TRUE                            1
#define FALSE                           0

/* These are our attribute numeric values */
#define BLACK                           0
#define BLUE                            1
#define GREEN                           2
#define RED                                     4
#define WHITE                           7
#define BRIGHT                          8
#define BLINKING                        0x80
#define UNDERLINE                       1
#define INVERSE                 0x70

unsigned char                   color(char *dbcolor);

int                      argc = 0;
char                    ** argv = (char **)0L;

far main()
{
                union REGS      registers;
                int                     temp;

                 DS = CS;  argc =  CX;  argv = MK FP( ES, DI);
 
                if ( argc == 6) {                               /* Have we enough
parameters? */
                        registers.h.ch = atoi( argv[0]);        /* Upper row    */
                        registers.h.cl = atoi( argv[1]);        /* Left column */
                        registers.h.dh = atoi( argv[2]);        /* Lower row    */
                        registers.h.dl = atoi( argv[3]);        /* Right column*/
                        registers.h.ah = 6;             /* Assume scroll up     */
                        temp = atoi( argv[4]);
                        if (temp < 0) {                 /* Negative means down */
                                registers.h.ah++;
                                temp = -temp;
                        }
                        registers.h.al = temp;
                        registers.h.bh = color( argv[5]);       /* Parse our color.
*/
                        int86(0x10,&registers,&registers);      /* Call BIOS */
                }
}

unsigned char color(char *dbcolor)
{
                unsigned char   badchar,ground,hue;
                badchar = ground = hue = 0;
                while (*dbcolor == ' ')
                        dbcolor++;
                while (*dbcolor && (!badchar)) {
                        switch (*dbcolor++) {
                                case    'r' :
                                case    'R' :
                                        hue |= (RED << ground);         /* Fore or
back"ground". */
                                        break;
                                case    'g' :
                                case    'G' :
                                        hue |= (GREEN << ground);
                                        break;
                                case    'b' :
                                case    'B' :
                                        hue |= (BLUE << ground);
                                        break;
                                case    'w' :
                                case    'W' :
                                        hue |= (WHITE << ground);
                                        break;
                                case    'i' :
                                case    'I' :
                                        hue |= INVERSE;
                                        break;
                                case    'u' :
                                case    'U' :
                                        hue |= UNDERLINE;
                                        break;
                                case    'x' :
                                case    'X' :
                                case    'n' :
                                case    'N' :
                                        break;
                                case    '/' :
                                        if (!ground)
                                                ground = 4;     /* Shift to
back"ground" */
                                        else
                                                badchar = TRUE;
                                        break;
                                case    '+' :
                                        hue |= BRIGHT;
                                        break;
                                case    '*' :
                                        hue |= BLINKING;
                                        break;
                                default :
                                        badchar = TRUE;         /* We've reached a
bad character */
                        }
                }
                return(hue);
}

----------------------------------------------------------------------
DD.C
/*
                DD.C  A library for disk and directory management

                This is a full blown example of how to create a .BIN
                library.  Each function has a unique number.
*/

#include <dos.h>
#include <dir.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

enum funcnum {
                GETDRIVE=1,
                SETDRIVE,
                ISDRIVE,
                ISNETDRIVE,
                GETDIR,
                CHDIR,
                MKDIR,
                RMDIR,
                ISDIR
};

void dbstrcpy(char *dbstr, char *cstr);
void dbitoa(char *string, int value);

int                              argc = 0;
char                            ** argv = (char **)0L;
int                             errno = 0;      /* Needed by the library routines
*/
unsigned                version = 0;            /* Needed for a DOS version check.
*/

char                            tempdir[MAXDIR] = "", homedir[MAXDIR] = "";

#define                  osmajor                ((unsigned char) version)
#define                  osminor                (*((unsigned char *)(& version) +
1))

far main()
{
                int             retval;

                 DS =  CS;   argc =  CX;   argv = MK FP( ES, DI);
                if ( argc) {
                        if ( argc == 2) {
                                switch (atoi( argv[0])) {
                                        case GETDRIVE           :
                                                retval = getdrive();
                                                break;
                                        case SETDRIVE           :
                                                retval = setdrive();
                                                break;
                                        case ISDRIVE            :
                                                retval = isdrive();
                                                break;
                                        case ISNETDRIVE :
                                                retval = isnetdrive();
                                                break;
                                        case GETDIR             :
                                                retval = getdir();
                                                break;
                                        case CHDIR              :
                                                retval = cd();
                                                break;
                                        case MKDIR              :
                                                retval = md();
                                                break;
                                        case RMDIR              :
                                                retval = rd();
                                                break;
                                        case ISDIR              :
                                                retval = isdir();
                                                break;
                                        default                 :
                        /* Negative error code from dBASE IV "Invalid function" */
                                                retval = -87;
                                }
                        /* Turbo C does not reset this so I do it myself. */
                                errno = 0;
                        }
                        else
                                retval = -94;         /* "Wrong number of
parameters." */
                        dbitoa( argv[0],retval); /* Send the result back to dBASE.
*/
                }
}

int getdrive()
{
                if (* argv[1])
                        argv[1] = (char)getdisk() + 'A';
                return(0);
}

int setdrive()
{
                int             temp;
                if (* argv[1]) {
                        temp = (int)(toupper(* argv[1]) - 'A');
                        setdisk(temp);
                        if (temp == getdisk())
                                return(0);
                }
                return(0xf);                     /* Invalid drive specification
error code. */
}

int isdrive()
{
                int     temp,retval;
                temp = getdisk();
                retval = setdrive();
                setdisk(temp);
                return(retval);
}               

int isnetdrive()
{
                union REGS  registers;

                if (! version) {
                         AH = 0x30;
                        geninterrupt(0x21);
                         version =  AX;
                }
/* Only on DOS 3.10 or above! */
                if (( osmajor >= 3) && ( osminor >= 0xA)) { 
                        registers.h.bl = toupper(* argv[1]) - 'A' + 1;
                        registers.x.ax = 0x4409;
                        intdos(&registers,&registers);
                        if (registers.x.cflag)
                                return(registers.x.ax);
                        else
                                return((registers.x.dx & 0x1000) == 0);
                }
                return(0x32);  /* Network request not supported. */
}

int getdir()
{
                char    *temp;

                if (* argv[1]) {
/*  
                If a drive letter was specified, use it.
                Otherwise assume the default drive.
*/
                getcurdir(((* argv[1] != ' ') ? (toupper(* argv[1]) - 'A' + 1) :
0), tempdir);
                        if (!errno) {
                                temp =  argv[1];
                                *temp++ = '\\'; /* Add a leading backslash. */
                                dbstrcpy(temp,tempdir);
                                return(0);
                        }
                        else {
                                dbstrcpy( argv[1],"");          /* Blank out the
directory name. */
                                return(errno);
                        }
                }
                return(0);
}

int cd()
{
                if (* argv[1])
                        chdir( argv[1]);
                return(errno);
}

int md()
{
                if (* argv[1])
                        mkdir( argv[1]);
                return(errno);
}

int rd()
{
                if (* argv[1])
                        rmdir( argv[1]);
                return(errno);
}

int isdir()
{
                int homedrive,tempdrive,retval;

                homedrive = getdisk();                  /* Save our drive*/
                getcurdir(0,homedir);                           /* and our
directory. */
                if ( argv[1][1] == ':') {
                        tempdrive = (int)(toupper(* argv[1]) - 'A');
                        setdisk(tempdrive);
                        if (tempdrive != getdisk())
                                return(0xf);            /* Invalid Drive
Specification */
                }
                getcurdir(0,tempdir);
                retval = ((chdir( argv[1]) == 0) ? 0 : errno);
                chdir(tempdir);
                setdisk(homedrive);
                chdir(homedir);
                return(retval);
}

/* 
                The following functions duties are to copy a string or number
                to a dBase parameter area, padding it out with spaces if
                necessary, so that parameter lengths are unaffected.
*/

void dbstrcpy(char *dbstr, char *cstr)
{
                while (*dbstr && *cstr)
                        *dbstr++ = *cstr++;
                while (*dbstr)
                        *dbstr++ = ' ';
}

void dbitoa(char *string, int value)
{
                char            negative,len;

                if ((len = strlen(string)) != 0) {
                        if (value < 0) {
                                negative = TRUE;
                                value = -value;
                        }
                        else
                                negative = FALSE;
                        while (len && value) {
                                string[len] = (value % 10) + '0';
                                value /= 10;
                        }
                        if (value || (!len && negative))
                                while (*string)
                                        *string++ = '*';
                        else {
                                if (negative)
                                        string[len] = '-';
                                while (len)
                                        string[len] = ' ';
                        }
                }
}

----------------------------------------------------------------------------
DD.PRG
*DD.PRG                This is the dBASE UDF front end to DD.BIN.
*                               To make use of both, issue the two following
*                               commands.  Negative numbers correspond to 
*                               dBASE error numbers.
*
*                                       . SET PROC TO DD
*                                       . LOAD DD
*

FUNCTION GetDrive                                       && Syntax: GetDrive()
                PRIVATE temp 
                temp  = " "
                CALL Dd WITH 1,temp 
RETURN temp                                     && Returns a character expression.


FUNCTION SetDrive                                       && Syntax: SetDrive(<expC>)
                PARAMETER drive 
                IF TYPE("drive ") = "C"
                        RETURN CALL("Dd",2,drive )
                ENDIF
RETURN -9                                               && Returns an error number,
0=no error.


FUNCTION IsDrive                                        && Syntax: IsDrive(<expC>)
                PARAMETER drive 
                IF TYPE("drive ") = "C"
                        RETURN CALL("Dd",3,drive ) = 0
                ENDIF
RETURN .F.                                              && Returns a logical true
or false.

FUNCTION IsNetDr                                        && Syntax: IsNetDr(<expC>)
                PARAMETER drive 
                IF TYPE("drive ") = "C"
                        RETURN CALL("Dd",4,drive ) = 0
                ENDIF
RETURN .F.                                              && Returns a logical true
or false.


FUNCTION GetDir                                 && Syntax: GetDir(<expC>)
                PARAMETER drive 
                PRIVATE temp 
                temp  = IIF("" = drive ," ",LEFT(drive ,1)) + SPACE(62)
                CALL Dd WITH 5,temp 
RETURN TRIM(temp )                              && Returns a character expression.


FUNCTION ChDir                                          && Syntax: ChDir(<expC>)
                PARAMETER dire 
                IF TYPE("dire ") = "C"
                        RETURN CALL("Dd",6,dire )
                ENDIF
RETURN -9                                                       && Returns an error
number, 0=no error.


FUNCTION MkDir                                          && Syntax: MkDir(<expC>)
                PARAMETER dire 
                IF TYPE("dire ") = "C"
                        RETURN CALL("Dd",7,LTRIM(TRIM(dire )))
                ENDIF
RETURN -9                                               && Returns an error number,
0=no error.


FUNCTION RmDir                                          && Syntax: RmDir(<expC>)
                PARAMETER dire 
                IF TYPE("dire ") = "C"
                        RETURN CALL("Dd",8,LTRIM(TRIM(dire )))
                ENDIF
RETURN -9                                                       && Returns an error
number, 0=no error.


FUNCTION IsDir                                          && Syntax: IsDir(<expC>)
                PARAMETER dire 
                IF TYPE("dire ") = "C"
                        RETURN CALL("Dd",9,LTRIM(TRIM(dire ))) = 0
                ENDIF
RETURN .F.                                              && Returns a logical true
or false.


FUNCTION PathSpec                                       && Syntax: PathSpec(<expC>)
                *Returns a proper full DOS path spec. as in "C:\DBASE\SAMPLES"
                PARAMETER drive 
                PRIVATE temp 
                temp  = IIF("" = drive ,GetDrive(),drive )
                IF IsDrive(temp )
                        RETURN UPPER(LEFT(temp ,1)) + ":" + GetDir(temp )
                ENDIF
RETURN ""                                               && Returns a character
expression.

* EOF. DD.PRG

