Accessing the Borland Database Engine (BDE) from Visual C++

The Borland Database Engine (BDE) is the database engine provided by Borland (now Inprise) for access to Paradox and dBase databases s well as a few other formats.
It provides the database interface for Borland products such as Borland C++, Borland C++ Builder, Borland Delphi, and Borland J Builder.
Borland provides library files and header files to facilitate direct access to the BDE API. Borland also provides fairly extensive documentation of each API function, usually including samples in C and Pascal. Borland compilers, such as Borland C++ 4.5 and 5.0 also include example programs using direct API calls.


Borlands’s equivalent of MFC is the Object Windows Library (OWL). OWL provides a C++ interface for the BDE. However, with the decline in popularity of Borland compilers in favor of Visual C++ and MFC, and the almost total disappearance of Borland’s support for OWL in favor of component based compilers such as C++ Builder, an MFC based class interface to the BDE API is now much more desireable.


Unfortunately, the API function calls can be quite complex and multiple API function calls are required just to get a database field value. Any function may fail returning an error code that must always be checked. Therefore, it was well worth my time to develop a class wrapper and associated exception handling class for the BDE API function calls. This article presents these classes with a test-bed program (shown below) for reading and writing data to and from a table using each of the available Paradox data types.


The classes provided here make extensive use of the MFC classes CString and COleDateTime for reading and writing string and date/time information to and from tables. This article also provides a nice example of the development and use of user-defined CException-derived exception handlers, with enhanced error messages.



Shown above is the test application. In the example data table, there is one field for each type. Some types have two entries in the dialog above in order to test the type conversions possible in the CBdeDatabase class. One field accepts a field value as a string, and the other accepts a field value in it’s native type (e.g., you can set an integer field by passing an integer value, or a string value).

Why would you want to access the Borland Database Engine from Visual C++ programs?


  1. Paradox tables past version 5.0 are not supported by Jet. If you want to access these tables, direct use of the BDE is the best way.
  2. Although I have not tested this class or the BDE with multi-threading, the BDE, unlike Jet, is supposedly safe for multi-threading.
  3. BDE provides access to dBase files with the cumbersome COM overhead of Jet or ADO, and does not use those horrid OLEVARIANT types!
  4. Database access through Microsoft seems to be getting more complicated rather than easier and they keep changing the APIs. The BDE API has stayed the same since the beginning.
  5. Speed! Although I haven’t done formal testing, this class for direct access to the BDE provides much faster database access than Jet or even the Borland Delphi database objects. I have never seen any database access as fast as the applications in which I used this class.

How to set up a project to access the BDE

1. You must have the BDE installed on your computer. There are various versions of the BDE out there so pay attention to version control issues.


2. The BDE path must be included in your PATH environment variable for the computer. BDE applications don’t normally add this, so you will probably have to do it yourself.


3. Borland’s web site provides the .lib file file that you must link with your project. These are also included with this example project.


4. Borland’s web site also provides the Ms-idapi.h header file that you must include in your project. These are also included with this example project.


5. Includes the files provided here in your project. These include


BdeDatabase.h
Interface for the BDE API class wrapper.
BdeDatabase.cpp
Implementation of the BDE API class wrapper.
BdeException.h
Interface for the BDE exception handler.
BdeException.cppp
Implementation of the BDE exception handler.

CBdeDatabase — The BDE Class Wrapper


The CBdeDatabase class provided here allows you to read and write data to and from dBase and Paradox tables using all non-BLOB types, and simple memo strings for BLOBs in Paradox tables (it crashes when reading dBase memos for some reason). Related functionality such as getting field names, and table navigation is also supported.


Essentially, one instance of the class is create for each table you want to access. The order of events is quite simple.


  1. Call Initialize at program startup to initialize the BDE.
  2. To open a connection to a table, call OpenDatabase providing the table name and path.
  3. Perform your operations on the table.
  4. Call CloseDatabase to disconnect from the table.
  5. Call Uninitialize before program exit.


The CBdeDatabase class here provides only a small fraction of the functionality of the BDE API, although for many projects, it is the most important.

/////////////////////////////////////////////////////////////////
// BdeDatabase.h — Interface for the CBdeDatabase class
// This class provides access to the Borland Database Engine
// For this to work, the BDE directory must be in the computers PATH statement
// Link with Idapi32m.lib,

#ifndef __BDEDATABASE_H__
#define __BDEDATABASE_H__

//#include “Ms-idapi.h” // header file for BDE API calls

// Actually, we are supposed to include Ms-idapi.h, but all it has is
// the following three lines anyway. This allows me to keep idapi.h in the project directory
#define __FLAT__
#define __WIN32__
#include “idapi.h”

#define TABLETYPE_PARADOX 0
#define TABLETYPE_DBASE 1
#define TABLETYPE_TEXT 2

#define EDITMODE_NONE 0
#define EDITMODE_APPEND 1
#define EDITMODE_INSERT 2
#define EDITMODE_EDITINPLACE 3

class CBdeDatabase
{
// Construction
public:
CBdeDatabase();
~CBdeDatabase();

// Attributes
public:

protected:
hDBIDb m_hDb; // Handle to the Database
hDBICur m_hCursor; // Handle to the cursor

CHAR m_szTableName[DBIMAXNAMELEN];
CHAR m_szDatabaseName[255];
CHAR m_szPrivateDir[255];
pBYTE m_pEditRecordBuffer;
UINT m_nEditMode;
int m_nTableType;

// Operations
public:
// functions to open and close databases
BOOL OpenDatabase(LPCTSTR szPath, LPCTSTR szTableName, int nTableType = TABLETYPE_PARADOX,
BOOL bReadOnly = FALSE, BOOL bExclusive = FALSE, LPCTSTR szPrivateDir = NULL);
BOOL OpenDatabase(LPCTSTR szFullPath,
BOOL bReadOnly = FALSE, BOOL bExclusive = FALSE, LPCTSTR szPrivateDir = NULL);
BOOL CloseDatabase();

// Table navigation
void MoveFirst();
void MoveNext();
void MovePrior();
void MoveLast();
LONG GetRecordCount();
BOOL IsBOF();
BOOL IsEOF();

// Functions to get field information
int GetFieldCount();
CString GetFieldName(int nFieldNumber);
int FieldNumberFromName(LPCTSTR szFieldName);
int GetFieldSize(int nFieldNumber);
int GetFieldType(int nFieldNumber);
int GetBlobType(int nFieldNumber);

// functions to get field values
CString GetFieldAsString(UINT16 nFieldNumber, BOOL* pbIsBlank = NULL);
LONG GetFieldAsInteger(UINT16 nFieldNumber, BOOL* pbBlank = NULL);
double GetFieldAsFloat(UINT16 nFieldNumber, BOOL* pbIsBlank = NULL);
COleDateTime GetFieldAsDate(UINT16 nFieldNumber, BOOL* pbBlank);
BOOL GetFieldAsBoolean(UINT16 nFieldNumber, BOOL* pbBlank = NULL);

// functions to set field values
BOOL SetFieldAsString(INT16 nFieldNumber, LPCTSTR szValue, BOOL bBlank = FALSE);
BOOL SetFieldAsInteger(INT16 nFieldNumber, int nValue, BOOL bBlank = FALSE);
BOOL SetFieldAsDate(INT16 nFieldNumber, COleDateTime dtValue, BOOL bBlank = FALSE);
BOOL SetFieldAsFloat(INT16 nFieldNumber, double fValue, BOOL bBlank = FALSE);
BOOL SetFieldAsBoolean(INT16 nFieldNumber, int nValue, BOOL bBlank = FALSE);

// functions for editing and posting operations
BOOL Edit();
BOOL Insert(); // insert and append really do the same thing
BOOL Append();
BOOL Post();
BOOL Cancel();
BOOL DeleteRecord();

protected:
// Error checking routines
BOOL CheckInitialization(LPCTSTR szOperation = NULL);
BOOL CheckValidCursor(LPCTSTR szOperation = NULL);
BOOL CheckEditMode(LPCTSTR szOperation = NULL);
BOOL CheckNotEditMode(LPCTSTR szOperation = NULL);

// Conversion routines
CString FormatDate(INT32 Date);
CString FormatTime(TIME Time);
CString FormatTimeStamp (TIMESTAMP TimeStamp);
COleDateTime TimeStampToOleDateTime(TIMESTAMP TimeStamp);
COleDateTime DateToOleDateTime(INT32 Date);
COleDateTime TimeToOleDateTime(TIME time);
DBIResult OleDateTimeToTimeStamp(COleDateTime dt, pTIMESTAMP pTimeStamp);
INT32 OleDateTimeToDate(COleDateTime dt);
TIME OleDateTimeToTime(COleDateTime dt);

BOOL OpenDatabaseHelper(int nTableType,
DBIOpenMode eOpenMode, DBIShareMode eShareMode, LPCTSTR szPrivateDir);
BOOL PrepareRecordEdit(int nEditMode);

// Inlines
public:
inline BOOL IsActive() {
return (m_hDb != NULL); }
inline BOOL GetEditMode() {
return (m_nEditMode != 0); }

protected:

// Statics
public:
static DBIResult Check(DBIResult ErrorValue, LPCTSTR szMessage = NULL);
static BOOL Initialize();
static void Uninitialize();

// Functions for enabling buttons
static BOOL EnableFirst(CBdeDatabase* pBdeDb);
static BOOL EnableNext(CBdeDatabase* pBdeDb);
static BOOL EnablePrior(CBdeDatabase* pBdeDb);
static BOOL EnableLast(CBdeDatabase* pBdeDb);
static BOOL EnableInsert(CBdeDatabase* pBdeDb);
static BOOL EnableEdit(CBdeDatabase* pBdeDb);
static BOOL EnablePost(CBdeDatabase* pBdeDb);
static BOOL EnableCancel(CBdeDatabase* pBdeDb);
static BOOL EnableAppend(CBdeDatabase* pBdeDb);
static BOOL EnableDelete(CBdeDatabase* pBdeDb);
static BOOL EnableOpen(CBdeDatabase* pBdeDb);
static BOOL EnableClose(CBdeDatabase* pBdeDb);

protected:
static BOOL m_bInitialized;

}; // end of class definition

#endif // __BDEDATABASE_H__

CBdeException — The BDE Exception Handler

To provide structured exception handling for my BDE class, I developed CBdeException. Even a simple task such as determining if a field exists requires a series of function calls, each of which could return an error code. Many CBdeDatabase member functions allocate memory as well which must be checked for failure. With each BDE API function call, I check the return code, and throw an exception on error.


Many API’s are notorious for providing terse and uninformative error messages, and the BDE API is no exception. For that reason, I have enhanced the error reporting the CBdeException class extensively, providing a detailed error message, the table and database name that produced the error, and finally the error string reported by the BDE.

#define __BDEEXCEPION_H__

//#include “Ms-idapi.h” // header file for BDE API calls

// Actually, we are supposed to include Ms-idapi.h, but all it has is
// the following three lines anyway. This allows me to keep idapi.h in the project directory
#define __FLAT__
#define __WIN32__
#include “idapi.h”

// These are additional errors that may be generated in the
// CBdeDatabase class
#define BDEEXERR_FIELDNOTINTEGER 1
#define BDEEXERR_FIELDNOTFLOAT 2
#define BDEEXERR_FIELDNOTDATE 3
#define BDEEXERR_FIELDNOTSTRING 4
#define BDEEXERR_NOSUCHFIELD 5
#define BDEEXERR_NOTINEDITMODE 6
#define BDEEXERR_ALREADYINEDITMODE 7
#define BDEEXERR_INVALIDCURSOR 8
#define BDEEXERR_ALREADYOPEN 9
#define BDEEXERR_NOTINITIALIZED 10
#define BDEEXERR_INVALIDDATETIMEFORMAT 11
#define BDEEXERR_UNSUPPORTEDFIELDTYPE 12
#define BDEEXERR_UNSUPPORTEDBLOBTYPE 13
#define BDEEXERR_FIELDNOTBOOLEAN 14
#define BDEEXERR_INVALIDFIELDINDEX 15
#define BDEEXERR_INVALIDFIELDNAME 16

class CBdeException : public CException
{
DECLARE_DYNAMIC(CBdeException);

// construction/destruction
public:
CBdeException();
CBdeException(DBIResult dbiResult);
CBdeException(DBIResult dbiResult, CString strTable,
CString strDatabaseName, LPCTSTR szAddInfo);
CBdeException(DBIResult dbiResult, UINT nExtendedError, CString strTable,
CString strDatabaseName, LPCTSTR szAddInfo);

// Attributes
public:

protected:
DBIResult m_dbiResult;
UINT m_nExtendedError;
CString m_strAddInfo;
CString m_strTableName;
CString m_strDatabaseName;

// Operations
public:
virtual BOOL GetErrorMessage(LPTSTR lpszError, UINT nMaxError,
PUINT pnHelpContext = NULL);
CString GetErrorMessage(BOOL bVerbose = TRUE);
virtual int ReportError(UINT nType = MB_OK, UINT nMessageID = 0);
static CString GetExtendedErrorMessage(int nError);

protected:

// inlines
public:
inline LPCTSTR GetTableName() {
return m_strTableName; }
inline LPCTSTR GetAddInfo() {
return m_strAddInfo; }
inline LPCTSTR GetDatabaseName() {
return m_strDatabaseName; }

}; // end of class definition

#endif __BDEEXCEPION_H__

Redistribution Issues


If you decide to write an MFC application with access to the BDE, be aware of a few redistribution issues. First, you must have the BDE directory in the PATH environment variable. Most BDE applications, such as those written in Delphi, do not require this, so you will probably have to do it yourself. Second, if you want to distribute the BDE, refer to the Borland redistribution agreement first. Although redistribution is free, they have had some unusual caveats in the past.


Where to go for More Information


This CBdeDatabase class provides a nice foundation for additional database functions such as creating tables or performing queries. If you want to do additional development, you will definately need a Borland development product. Borland C++ 5.0 has a large selection of example BDE applications in C accessing the BDE API directly. Also, Borland provides a good on-line help file and written documentation of all of the API functions with C source examples of each in many cases. Even so, the C examples are usually not sufficient to figure out how to make some API calls without the more complete examples provided with the Borland development products.

Downloads

Download demo project (VC++ Files only)- 286 KB This includes the VC++ project for the test application, test tables, and the lib and header files for linking with BDE. It does not include the BDE, and if you do not have the BDE installed already, the demo program will not be very exciting! Don’t forget to add the BDE directory to your PATH environment variable.

Download source – 17 KB This includes the source for the CBdeDatabase class and the CBdeException class.

History

More by Author

Get the Free Newsletter!

Subscribe to Data Insider for top news, trends & analysis

Must Read