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



Comments

  • path

    Posted by Legacy on 11/11/2002 12:00am

    Originally posted by: mike

    i set the path variable in WINNT 4.0 with this command:

    c:>set path=C:\bdepath;%path%

    but when i start my programm with your api, the message 'idapi.dll' not found at the following path!

    if i type path on the commandline the bdepath is included!

    whats the problem?

    Reply
  • How to lock a record ?

    Posted by Legacy on 09/19/2001 12:00am

    Originally posted by: ck

    I have a single-row database table, which i decide to open it (not locking it), yet locking the one and only record of the table.
    how may i do this ?jibai la

    Reply
  • Multiply load idapiXX.dll

    Posted by Legacy on 01/19/2000 12:00am

    Originally posted by: Andriy Maksymov

    The problem takes place when we use these classes.
    
    I have found the mistake: it is in CBdeDatabase::Initialize() routine. The correct code is folowing:

    BOOL CBdeDatabase::Initialize()
    {
    // make sure not already initialized
    if (m_bInitialized) return TRUE;

    // initialize the BDE
    DBIEnv dbiEnv;
    memset(&dbiEnv, 0, sizeof(dbiEnv));
    strncpy(dbiEnv.szLang, SIDAPILangID, sizeof(dbiEnv.szLang)-1);
    DBIResult dbiResult = DbiInit( &dbiEnv );
    if (dbiResult != DBIERR_NONE && dbiResult != DBIERR_MULTIPLEINIT)
    throw new CBdeException(dbiResult, ...);
    ... and so on.

    SIDAPILangID is a constant. We must define it early
    #define SIDAPILangID "0009"

    I've tested it and it works fine.
    I have further development the classes and I can mail their anybody, but I want to remind everyone that BDE is commercial product and distributed under own licence. So, I don't want that my work will be used as pirate software.

    Best regards.

    Reply
  • BDE - License

    Posted by Legacy on 12/31/1999 12:00am

    Originally posted by: Victor R�der

    In the license-text of BDE you can read something like that:
    You are only allowed to give BDE away with applications which are built with Inprise - products, like Delphi or C++ Builder.

    I think, reading this license-text is very very important for everyone, who uses CBdeDatabase in MSVC++ - application.

    Reply
  • BDE And Paradox 7.00

    Posted by Legacy on 12/13/1999 12:00am

    Originally posted by: KeatSin

    I use the BDE class to develop a program in Visual C++, but the problem I encounter is that I can't run the Paradox 7.00 during my program is running what is the possible caused for this.It give the error message
    "Could not initialize BDE.:
    Trying to load multiple IDAPIxx.DLL"
    Please help

    Reply
  • Fix for dBase Memo crash

    Posted by Legacy on 06/27/1999 12:00am

    Originally posted by: Alexander Ashley-Carrington

    "simple memo strings for BLOBs in Paradox tables (it crashes when reading dBase memos for some reason)"

    The reason for this was that you copied the BLOB into pDestBuf in GetFieldAsString() when the buffer wasn't allocated. The problem is that the field size reported by DbiGetFieldDescs() for a BLOB type for dBase is zero, so no buffer is allocated.

    The fix I used was if a zero length field is reported by DbiGetFieldDescs(), malloc() at least 1 byte, then in the case fldBLOB switch realloc() pDestBuf to a suitable size then copy the BLOB (memo). The rest of the code cleans up the allocated memory accordingly.

    By the way, thanks for you efforts, it was just what I needed to solve a problem I was having. I have expanded somewhat on your original design and will post it here when it's finished (this could mean never though!).

    Reply
  • Missing DLL

    Posted by Legacy on 06/24/1999 12:00am

    Originally posted by: Jacob Bensabat

    Hi,
    The code does not work since you have provided the lib file
    but not the DLL file.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

  • Live Event Date: September 17, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Another day, another end-of-support deadline. You've heard enough about the hazards of not migrating to Windows Server 2008 or 2012. What you may not know is that there's plenty in it for you and your business, like increased automation and performance, time-saving technical features, and a lower total cost of ownership. Check out this upcoming eSeminar and join Rich Holmes, Pomeroy's practice director of virtualization, as he discusses the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds