Download demo project - 223 Kb
Download source - 16.2 Kb
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?
- 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.
- Although I have not tested this class or the BDE with multi-threading, the BDE, unlike Jet, is supposedly safe for multi-threading.
- BDE provides access to dBase files with the cumbersome COM overhead of Jet or ADO, and does not use those horrid OLEVARIANT types!
- 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.
- 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.
- Call Initialize at program startup to initialize the BDE.
- To open a connection to a table, call OpenDatabase providing the table name and path.
- Perform your operations on the table.
- Call CloseDatabase to disconnect from the table.
- 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.
#ifndef __BDEDATABASE_H__
#define __BDEDATABASE_H__
#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
{
public:
CBdeDatabase();
~CBdeDatabase();
public:
protected:
hDBIDb m_hDb;
hDBICur m_hCursor;
CHAR m_szTableName[DBIMAXNAMELEN];
CHAR m_szDatabaseName[255];
CHAR m_szPrivateDir[255];
pBYTE m_pEditRecordBuffer;
UINT m_nEditMode;
int m_nTableType;
public:
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();
void MoveFirst();
void MoveNext();
void MovePrior();
void MoveLast();
LONG GetRecordCount();
BOOL IsBOF();
BOOL IsEOF();
int GetFieldCount();
CString GetFieldName(int nFieldNumber);
int FieldNumberFromName(LPCTSTR szFieldName);
int GetFieldSize(int nFieldNumber);
int GetFieldType(int nFieldNumber);
int GetBlobType(int nFieldNumber);
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);
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);
BOOL Edit();
BOOL Insert();
BOOL Append();
BOOL Post();
BOOL Cancel();
BOOL DeleteRecord();
protected:
BOOL CheckInitialization(LPCTSTR szOperation = NULL);
BOOL CheckValidCursor(LPCTSTR szOperation = NULL);
BOOL CheckEditMode(LPCTSTR szOperation = NULL);
BOOL CheckNotEditMode(LPCTSTR szOperation = NULL);
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);
public:
inline BOOL IsActive() {
return (m_hDb != NULL); }
inline BOOL GetEditMode() {
return (m_nEditMode != 0); }
protected:
public:
static DBIResult Check(DBIResult ErrorValue, LPCTSTR szMessage = NULL);
static BOOL Initialize();
static void Uninitialize();
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;
};
#endif
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__
#define __FLAT__
#define __WIN32__
#include "idapi.h"
#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);
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);
public:
protected:
DBIResult m_dbiResult;
UINT m_nExtendedError;
CString m_strAddInfo;
CString m_strTableName;
CString m_strDatabaseName;
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:
public:
inline LPCTSTR GetTableName() {
return m_strTableName; }
inline LPCTSTR GetAddInfo() {
return m_strAddInfo; }
inline LPCTSTR GetDatabaseName() {
return m_strDatabaseName; }
};
#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.