community.borland.com

Article #15177: Introduction to BDE Programming

 Technical Information Database

TI177B.txt   Introduction to BDE Programming
Category   :General Programming
Platform    :All
Product    :BDE  All

Description:
         Introduction to BDE Programming
                    BDE v2.0


I) Project Setup
  This section covers the basics of what needs to be
  done in order to set up a Borland Database Engine
  project or makefile.

II) Design Overview
  A high level overview of the steps that are required
  to create a simple application which retrieves fields
  from a table

III) Design Specifics
  Detailed description, with code examples, of the
  steps described in part II.

IV) Error Handler
  This section contains the error handler used by this
  example.


I) Project Setup
================

This document provides a quick introduction to programming
with the Borland Database Engine. By the end of this section,
you should have a simple EasyWin, BDE example which gets
a record from a table and displays the first two fields.

The following steps must be followed when starting to write a
BDE application.

First, set up the project or makefile
MAIN.CPP     - File to contain your code
DBERR.CPP    - Error handling routine
IDAPI.LIB    - BDE Import Library
MAIN.DEF     - Module Definition file

Select the large memory model.

For this simple application, set the target to be an EasyWin
application. This way we don't have to deal with any Windows UI
issues.

It is also suggested to install the IDAPI.TOK file so as to have
syntax hilighting for BDE functions and types (directions on
this are included in the \BDE\DOC\IDAPITOK.TXT file).

The on-line help file, IDAPI.HLP, can also be incorporated into
the BC 4.5 OpenHelp architecture, allowing context sensitive
help on IDAPI types and functions.

Other required setup:
---------------------

The Borland Database Engine is fairly stack intensive,
especially when doing batch operations and queries, so it is
generally recommended that the stack be set to a minimum of 25k
(for Windows C/C++ applications, the stack size is set in the
module definition file)

Increase the number of file handles available to the application.
By default, the BDE assumes that it has access to 48 physical
file handles (set by the BDE Configuration utility,  System
page | MAXFILEHANDLES option, 5-255). Due to this default
setting, it is usually best to set the number of file handles
available to an application to a minimum of 68 using the Windows
API function SetHandleCount. Seemingly random errors can occur
when the BDE does not have access to enough file handles.

Use the Debug Layer when developing applications. The debug layer
performs much stricter error checking than the regular DLL,
resulting in fewer GP faults and less re-booting of your machine.
It will also produce a trace output of which IDAPI functions were
called by an application. Note that use of the debug layer
requires the use of both the Debug DLL (Set using the DLLSwap
utility), as well as a call to the DbiDebugLayerOptions function.

Make certain to compile with 'Allocate enums as ints' selected
(in the BC 4.x IDE, Options | Project | Compiler | Code 
Generation). A number of structures, such as CURProps, make use
of enumerations. Within the DLL, these are allocated as two 
byte values. Turning off this option will result in your code 
passing only one byte. This error generally manifests itself 
with stack corruption problems, such as GP faults when calling
or returning from a function.

Within a module to contain BDE code, include the following header
files:
WINDOWS.H
IDAPI.H

Note that order is important. The WINDOWS.H file must be included
before IDAPI.H.


II) Design Overview
===================

Before we start writing code, a brief overview of what needs to
be done to get a record from a tables:

Increase the number of file handles available to the application
Initialize the Borland Database Engine
Enable the debug layer
Open a database object
Set the database object to point to the directory containing the
  table
Set the directory for temporary objects
Open a table, creating a cursor object
Get the properties of the table
Using these properties, allocate memory for a record buffer
Position the cursor on the desired record
Get the desired record from the cursor (table)
Get the desired field(s) from the record
Free all resources

Note that throughout the following short example unfamiliar
variable types may be used. These are IDAPI variable types that
are defined in the IDAPI.H header file. For example, BYTE,
BOOL, CHAR, and FLOAT.

III) Design Specifics
=====================

Increase the number of file handles available to the application
----------------------------------------------------------------

A call to the Windows API function SetHandleCount will
increase the number of file handles available to an application
in the Windows environment:

int    HandlesAvail;
int    HandlesWanted;

HandlesAvail = 0;
HandlesWanted = 68;

HandlesAvail = SetHandleCount(HandlesWanted);
if (HandlesAvail < HandlesWanted)
{
    // Display message re: not enough available
    // file handles
    return;
}

Note that in non-trivial cases it is recommended to determine
the number of file handles that are specified in the BDE
configuration file. This would be done using the
DbiOpenCfgInfoList function, which will be described in a
later section on configuration management.

Initialize the Borland Database Engine
--------------------------------------

The Borland Database Engine is initialized using the DbiInit
function:

    CHKERR(DbiInit(NULL));

Note that the CHKERR macro is defined in the DBERR.H
file, which is a part of the Error Handling module. The call
to DbiInit allocates system resources for the client.

Enable the Debug layer
----------------------

The following code is used to enable the debug layer,
outputting trace information to a text file on disk.

DbiDebugLayerOptions(DEBUGON | OUTPUTTOFILE, "MYTRACE.INF");

Note that in certain situations, you may also want to use the
FLUSHEVERYOP flag, which will force output to the trace
file after every operation. While this is slower, it is useful
when a GP fault occurs....

Open a database object
----------------------

The next step that is needed is to open a database object. All
table access must be performed within the context of a
database. Local databases generally use what is referred to as
the "STANDARD" database, which is what we will be using in this
example. The preferred method would be to create an alias to a
local directory and using that as the database. This allows for
easy modification in the future if one day it is decided to move
the application from using dBASE tables to using InterBase
tables. The DbiOpenDatabase function is used to open a database:

hDBIDb   hDb;    // Handle to the Database

hDb = 0; // Initialize to zero for cleanup
         // purposes

CHKERR_CLEANUP(
    DbiOpenDatabase(NULL, // Database name
                          // - NULL for standard Database
                    NULL, // Database type -
                          // NULL for standard Database
                    dbiREADWRITE, // Open mode - Read/Write or
                          // Read only
                    dbiOPENSHARED, // Share mode - Shared or
                          // Exclusive
                    NULL, // Password - not needed for the
                          // STANDARD database
                    NULL, // Number of Optional Parameters
                    NULL, // Field Desc for Optional
                          // Parameters
                    NULL, // Values for the
                          // Optional Parameters
                    &hDb) // Handle to the database
                   );

// At the end of the function
CleanUp:
    // Close only if open
    if (hDb != 0)
    {
        DbiCloseDatabase(&hDb);
    }

Set the database object to point to the directory containing
------------------------------------------------------------
the table
---------

The working directory next needs to be set to the directory
containing the table. While it is possible to open a table in
other directories, specifying the absolute path, it is generally
recommended to open tables in the working directory, as a
number of operations, such as getting a list of available
tables, work off the current directory. The DbiSetDirectory
function is used to set the working directory (using the default
location of the BDE example tables):

CHKERR_CLEANUP(
    DbiSetDirectory(hDb, // Handle to the
                         // database which is being modified
                    "C:\\BDE\\EXAMPLES\\TABLES")
                         // The new working directory
                   );

Note that the full, absolute path needs to be used. Relative
paths are not supported.

Set the directory for temporary objects
---------------------------------------

While not all BDE applications create temporary objects, but
larger applications will at one time or another. For example,
the result set from a query or the records which cause a key
violation in a restructure will be placed in a temporary table.
By default, this temporary, or private directory, as it is
called, is the startup directory. This will cause a problem if
the application is run off a network or a CD-ROM., as the
directory cannot be shared, and it must we writable. The
DbiSetPrivateDir function is used to set the private directory
for a client:

CHKERR_CLEANUP(
    DbiSetPrivateDir("c:\\")
      //  Select a directory on a local drive that is not
      //  used by other applications.
                    );

Note that the full, absolute path needs to be used. Relative
paths are not supported.

Open a table, creating a cursor object
--------------------------------------

Next, open the table. Upon opening a table, a cursor object is
created and returned to the calling application. A cursor
object is basically an abstraction which allows queries and
tables to be accessed in the same method:

hDBICur hCur;    // Handle to the cursor (table)
CHAR    szTblName[DBIMAXNAMELEN]; // Table name - DBIMAXNAMELEN
                 // is defined in IDAPI.H
CHAR    szTblType[DBIMAXNAMELEN]; // Table Type

hCur = 0; // Initialize to zero for cleanup
          // purposes

strcpy(szTblName, "customer"); // Name of the table
strcpy(szTblType, szPARADOX);  // Type of the tables
                        // - szPARADOX is defined in IDAPI.H

CHKERR_CLEANUP(
    DbiOpenTable(hDb,  // Handle to the
                       // database to which this cursor will
                       // be related
                 szTblName, // Name of the table
                 szTblType, // Type of the table - only
                       // used for local tables
                 NULL, // Index Name - Optional
                 NULL, // IndexTagName - Optional.
                       // Only used by dBASE
                 0,    // IndexId - 0 = Primary. Optional.
                       // Paradox and SQL only
                 dbiREADWRITE, // Open Mode -
                       // Read/Write or Read Only
                 dbiOPENSHARED, // Shared mode -
                       // SHARED or EXCL
                 xltFIELD, // Translate mode
                       // FIELD or NONE
                       // FIELD:  Convert data from table
                       //   format to C format
                       // NONE: Leave data in it's native
                       // state
                 FALSE,// Unidirectional -
                       // False means can navigate forward
                       // and back.
                 NULL, // Optional Parameters.
                 &hCur)// Handle to the cursor
                );

CleanUp:
    // Close only if open
    if (hCur != 0)
    {
        DbiCloseCursor(&hCur); // Note the use of DbiCloseCursor
                               // - there is no DbiCloseTable.
    }

Get the properties of the table
-------------------------------

We next need to determine the size of the record buffer for
the table. This information is gotten from the cursor via the
DbiGetCursorProps function. The Cursor properties include
information on the table name, size, type, number of fields,
and record buffer size. More information can be found on
cursor properties in the on-line help under CURProps.

CURProps    curProps; // Properties of the cursor

CHKERR_CLEANUP(
    DbiGetCursorProps(hCur,      // Handle to the cursor
                      &curProps) // Properties of the cursor
                                 // (table)
              );

curProps.iRecBufSize contains the size of the record buffer.

Using these properties, allocate memory for a record buffer

pBYTE       pRecBuf; // Pointer to the record buffer

pRecBuf = NULL; // For cleanup purposes

// Allocate memory for the record buffer
pRecBuf = (pBYTE)malloc(curProps.iRecBufSize * sizeof(BYTE));
if (pRecBuf == NULL)
{
    // Display some error message
    goto CleanUp;
}

CleanUp:
    if (pRecBuf)
    {
        free(pRecBuf);
    }

Position the cursor on the desired record
-----------------------------------------

The DbiSetToBegin() function is used to position the cursor
to the "crack" before the first record in the table. Crack
semantics basically allow for the current location of the cursor
to be before the first record, between records, or after the
last record. One of the reasons for having crack semantics is to
allow the use of one function to access all records in a table.
For example, in place of having to use DbiGetRecord the first
time, and DbiGetNextRecord each subsequent time, it is now
possible to use DbiGetNextRecord to get all records in a table.

CHKERR_CLEANUP(
    DbiSetToBegin(hCur) // Position the specified cursor
                        // to the crack before the first record
              );

Get the desired record from the cursor (table)
----------------------------------------------

The DbiGetNextRecord function is normally used to get a
record from a table. Note that the current record of the cursor
is set to the record returned by this function (the next record
in the table):

CHKERR_CLEANUP(
    DbiGetNextRecord(hCur,      // Cursor from which to get the
                                // record.
                     dbiNOLOCK, // Lock Type
                     pRecBuf,   // Buffer to store the record
                     NULL)      // Record properties -
                                // don't need in this case
                    );

Get the desired field(s) from the record
----------------------------------------

The last step is to get the field values out of the record
buffer and into some local variable. Note that in this case we
are making assumptions about which field is at which ordinal
position within the table, as well as the size of the field. In
general, it is recommended to use DbiGetFieldDescs to get
information about a field before retrieving it. Also note that a
single function, DbiGetField, is used to get all fields, other
than BLOBs, from a table.

FLOAT       custNum;
BOOL        isBlank;

CHKERR_CLEANUP(
    DbiGetField(hCur,    // Cursor which contains the record
                1,       // Field Number of the "Customer" field.
                pRecBuf, // Buffer containing the record
                (pBYTE)&custNum, // Variable for the Customer
                         // Number
                isBlank) // Is the field blank?
           );

Free all resources
------------------

After all desired operations have been performed, the
resources allocated on behalf of the application need to be
cleaned up. In addition to any memory explicitly allocated
using malloc or new, all engine objects must also be cleaned
up, including the cursor, database, and engine:

CleanUp:
    if (pRecBuf)
    {
        // Free the record buffer
        free(pRecBuf);
    }
    if (hCur !=0)
    {
        // Close the cursor
        DbiCloseCursor(&hCur);
    }
    // Close only if open
    if (hDb != 0)
    {
        // Close the database
        DbiCloseDatabase(&hDb);
    }

    // Close the BDE.
    DbiExit();

IV) Error handler

// DBERR.H

// This file contains macros to use around BDE functions
// which are not expected to fail, or which would
// result in a fatal error. More explicit error handling needs
// to be done in the application when a non-fatal
// error can occur, for example, getting an End of file error,
// DBIERR_EOF, from a DbiGetNextRecord
// loop.

#ifndef __DBERR_H
#define __DBERR_H

#include 

// Macro to use when no cleanup is required

#define CHKERR(parm) DBIError(__FILE__, __LINE__, \
                            #parm, parm) ; \
                            if (GlobalDBIErr) { \
                               return GlobalDBIErr ;}

// Macro to use when cleanup must be done within a function
// for example, to close open tables or to free allocated memory.
// Note: must have a CleanUp label defined in each function
// where this macro is used. The CleanUp
// label should start the section of a function which frees the
// resources allocated within a given function

#define CHKERR_CLEANUP(parm) DBIError(__FILE__, __LINE__, \
                            #parm, parm) ; \
                            if (GlobalDBIErr) { \
                               goto CleanUp ;}

// Global variable to contain the result code
extern DBIResult GlobalDBIErr;

// Prototype for the error handling function
DBIResult DBIError(pCHAR, UINT16, pCHAR, DBIResult);

#endif


// DBERR.C

#include 
#include "DBERR.h"

//      The way to use this macro is to include the DBERR.H.
//      Then pass an IDAPI function as a parameter to the
//      macro:
//
//      #define CHKERR(parm) DBIError(__FILE__, __LINE__, \
//                                   #parm, parm) ; \
//                                   if ( GlobalDBIErr ) { \
//                                       return GlobalDBIErr ;}
//
//      You would then use it as such:
//          CHKERR(DbiCreateTable(hDb, bOverWrite,
//                                &crTblDsc)) ;

// Global variable to hold return value from BDE functions
DBIResult GlobalDBIErr;

// Global variables to contain the error messages. Defined
// globally to ensure that an error message can be displayed
// even if the system is out of memory (and to keep it off
// the stack)

// Contains just the BDE error message
static char szDBIStatus[(DBIMAXMSGLEN * 7)+1];

// Contains the entire message, including file name and line
// number of the offending function.
static char szMessage[(DBIMAXMSGLEN * 7)+1+110];

//==========================================
//  Function:
//          DBIError();
//
//  Input:  module name (pCHAR), line number (UINT16),
//             Engine function name (pCHAR),
//             Result (DBIResult)
//
//  Return: A DBIResult value.
//
//  Description:
//          This is a function which takes in the information of
//          where the error occurred and displays error
//          information in a message box.
//==============================================================
DBIResult
DBIError (pCHAR module, UINT16 line, pCHAR function,
          DBIResult retVal)
{
    DBIErrInfo  ErrInfo; // Structure to contain the error
                         // information

    if (retVal == DBIERR_NONE)
    {
        GlobalDBIErr = DBIERR_NONE;
        return retVal;
    }
    // Don't want to call functions if the DLL is not there....
    if (retVal != DBIERR_CANTFINDODAPI)
    {
        // Get as much error information as possible
        DbiGetErrorInfo(TRUE, &ErrInfo);

        // Make certain information is returned on the correct
        // error (that this function was called
        // immediately after the function that caused the error.
        if (ErrInfo.iError == retVal)
        {
            strcpy(szDBIStatus, ErrInfo.szErrCode);

            if (strcmp(ErrInfo.szContext1, ""))
            {
                strcat(szDBIStatus, ErrInfo.szContext1);
            }
            if (strcmp(ErrInfo.szContext2, ""))
            {
                strcat(szDBIStatus, ErrInfo.szContext2);
            }
            if (strcmp(ErrInfo.szContext3, ""))
            {
                strcat(szDBIStatus, ErrInfo.szContext3);
            }
            if (strcmp(ErrInfo.szContext4, ""))
            {
                strcat(szDBIStatus, ErrInfo.szContext4);
            }
        }
        else {
            DbiGetErrorString(retVal, szDBIStatus);
        }

        sprintf(szMessage,
                "Module:\t\t%s\nFunction:\t%s\nLine:\t\t%d\n"
                "Category:\t%d\nCode:\t\t%d\nError:\r\n\r\n%s\n",
                module, function, line, ErrCat(retVal),
                ErrCode(retVal), szDBIStatus);

        MessageBox(NULL, szMessage, "BDE Error",
                   MB_ICONEXCLAMATION);
    }
    else
    {
       // Display an error message if the BDE DLL's cannot be
       // found.
       MessageBox(NULL, "Cannot find Borland Database"
                  " Engine files: Check the [IDAPI] section"
                  " in WIN.INI.",  "BDE Initialization Error",
                  MB_ICONHAND | MB_OK);
    }

    GlobalDBIErr = retVal;
    return retVal;
}


Reference:


7/15/98 3:24:21 PM
 

Last Modified: 01-SEP-99