facebooklinkedinrsstwitterBlogAsset 1PRDatasheetDatasheetAsset 1DownloadForumGuideLinkWebinarPRPresentationRoad MapVideofacebooklinkedinrsstwitterBlogAsset 1PRDatasheetDatasheetAsset 1DownloadForumGuideLinkWebinarPRPresentationRoad MapVideo

Using the Btrieve 2 API with C/C++

In this tutorial, we will learn how to connect to the Actian Zen database engine using the Btrieve 2 SDK and C++.

This page covers the topics listed at right.

Connect with the Zen Database

The Btrieve 2 SDK for Actian Zen provides a mature API that is ideal for working directly with data files and records. Unlike SQL-based database access, which uses table and column concepts, Btrieve 2 provides direct access to data records stored in individual files in the file system.

To use Btrieve 2 in a project, we need to do the following:

  • Install Actian Zen.
  • Download and unpack the Btrieve 2 SDK into the development environment.
  • Create the C++ application.

Since the Btrieve 2 API is implemented as a standard C++ class library, it integrates directly with your application environment.

Btrieve 2 works with any project configuration, and the included libraries support both 32-bit and 64-bit projects. The only adjustments required for project properties are to link the correct headers and libraries in the project so that they are visible to the compiler and linker.

Notice that there are two libraries.

  • btrieveCpp.lib is the C++ class library, which is a wrapper around the implementation defined in btrieveC.lib, and must be listed as an additional dependency in the linkage editor settings.
  • btrieveC.lib is the C library that contains the implementation and talks directly to the database engine. It is omitted from the additional dependency list since it is a dependency of btrieveCpp.lib.

The 32-bit and 64-bit libraries themselves have the same names, btrieveCpp.lib and btrieveC.lib, but are provided in the SDK package under the win32 and win64 folders, respectively.

The same header files in the include folder are used by all configurations. Both headers are required, though only the first, btrieveCpp.h, is included directly, while the second is included indirectly.

  • btrieveCpp.h corresponds to btrieveCpp.lib and is directly included in your program.
  • btrieveC.h corresponds to btrieveC.lib and is included indirectly via btrieveCpp.h.

To interact with a Zen engine via the Btrieve 2 API, we must first import the btrieveCpp.h header in the application code:

#include "btrieveCpp.h"

This is assumed for all other examples in the guide, so we won’t repeat this line for every example.

To connect to the engine, you must create an instance of the btrieveClient object:

BtrieveClient btrieveClient (0x4232, 0) ;

The first parameter is a serviceAgentIdentifier (0x4232 = “B2”) which identifies each instance of your application to the engine. It can be any two-byte value larger than “AA” (0x4141).

The second parameter is a 2-byte integer clientIdentifier. If you are writing a multithreaded application, each thread needs to provide a unique client identifier to the engine. We will just use 0 in our simple example.

Create Data Files and Insert Data

In order to start storing data, we need to create a data file and add records to it. So, in this section, we will examine:

  • Creating a file
  • Inserting a data record

Creating a File

All Zen data is stored in files. Each file stores data in records, which contain bytes of data. Typically, the records in a given file represent a collection of related data values.

In our example, we are creating a file to store blood pressure readings. Each record will consist of the following information:

  • 8-byte time stamp for when the BP reading was taken
  • 2-byte integer for a systolic value
  • 2-byte integer for a diastolic value
  • 1-byte character for an evaluation code

Given the above data, we need to create a file to hold 13-byte records. The snippet below defines a BPrecord_t structure that accommodates the blood pressure record. The #pragma pack statements are needed to make sure the record is actually 13-bytes, with no extra alignment bytes added by the compiler.

#pragma pack(1)
typedef struct {
  uint64_t timeTaken;
  uint16_t systolic;
  uint16_t diastolic;
  char EvalCode;
} BPrecord_t;
#pragma pack()

To create a file for these records, we need to allocate a btrieveFileAttributes object and use the SetFixedRecordLength property to specify our record size.

Btrieve::StatusCode status;
BtrieveFileAttributes btrieveFileAttributes;
status = btrieveFileAttributes.SetFixedRecordLength(sizeof(BPrecord_t));

You can add other file attributes, but the record size is the only one that is required. For example, you could specify that the file be compressed before written to disk.

Next we will instruct the Zen engine to create the data file using the FileCreate() method on our btrieveClient session object. We can also specify a creation mode to indicate if the file should be overwritten if it already exists.

static char* btrieveFileName = (char*)"Pressures.btr";
status = btrieveClient->FileCreate(&btrieveFileAttributes, btrieveFileName,
                                                  Btrieve::CREATE_MODE_NO_OVERWRITE);
if ((status != Btrieve::STATUS_CODE_NO_ERROR) &
    (status != Btrieve::STATUS_CODE_FILE_ALREADY_EXISTS))
{
printf("Error: BtrieveClient::FileCreate():%d:%s.\n", status,
                   Btrieve::StatusCodeToString(status));
}

Above shows an example of error checking using the built in StatusCode enumerations in the Btrieve class. For simplicity, we will not show error checking in subsequent code samples.

Inserting a Data Record

Inserting data into a file is accomplished with the RecordCreate method of the BtrieveFile object. But before you can insert a record, you must open the file.

BtrieveFile btrieveFile;
status = btrieveClient->FileOpen(btrieveFile, btrieveFileName, NULL,
                                              Btrieve::OPEN_MODE_NORMAL);

In the above commands, we allocate a btrieveFile object and use our btrieveClient session to open the file we previously created. The third parameter can be used to pass in a Btrieve owner name, which is a password for securing (and optionally encrypting) your data file. Since our file does not have an owner name, we can just pass in NULL.

To insert a record, we allocate a record buffer for the BPrecord_t structure created earlier. Then we populate the members of the record structure with our data values and insert it, as shown by the following example.

Btrieve::StatusCode status = Btrieve::STATUS_CODE_NO_ERROR;
BPrecord_t record;
  // Get current system time and convert to microseconds 
time_t now = time(0) * 1000000;
  //Convert time to Btrieve 2 Timestamp format 
record.timeTaken = Btrieve::UnixEpochMicrosecondsToTimestamp(now);
  //sysdata and diasdata are provided at runtime 
record.systolic = sysdata; 
record.diastolic = diasdata;
  //Determine the EvalCode from the systolic & diastolic 
record.EvalCode = 'N';       // Default is Normal
if ((sysdata >= 120 and sysdata < 130) and (diasdata < 80)) 
     record.EvalCode = 'E';    //Elevated blood pressure
if ((sysdata >= 130 and sysdata < 140) or (diasdata >= 80 and diasdata < 90)) 
     record.EvalCode = 'H';      //High blood pressure
if ((sysdata >= 140 and sysdata <= 180) or
    (diasdata >= 90 and diasdata <= 120)) 
     record.EvalCode = 'V'; //Very high blood pressure
if ((sysdata > 180) or (diasdata > 120))
     record.EvalCode = 'C';  //Hypertensive Crisis 
// Insert the record 
status = btrieveFile->RecordCreate((char*)& record, sizeof(record));

Of course, you should check the status of the insert to make sure it was successful!

Index and Retrieve Data

So far, we have learned how to set up a session with the Actian Zen engine using a Btrieve 2 application, create a data file, and insert records into the file. The real power of Zen is seen in its indexing and fast data retrieval. We will examine this functionality next.

Indexing Records

One of the most powerful features of Zen files is their indexing. You can add indexes to existing data files so that you can easily and quickly retrieve records by a particular value or in a particular order. You can also add indexes to newly created files before any records have been inserted.

Index creation has three steps:

  1. Set up an index segment.
  2. Define index attributes.
  3. Add the index to the data file.

Continuing with our example file, we will add an index on the time stamp portion of the record. Note that you must open a file before you can add an index to it.

BtrieveKeySegment btrieveKeySegment;
BtrieveIndexAttributes btrieveIndexAttributes;

// Create a time stamp index segment on the first 8 bytes of the record 
status = btrieveKeySegment.SetField(0, 8, Btrieve::DATA_TYPE_TIMESTAMP);
// Add the segment to the Index object 
status = btrieveIndexAttributes.AddKeySegment(&btrieveKeySegment);
// Specify the nonmodifiable index attribute 
status = btrieveIndexAttributes.SetModifiable(false);
// Create the index 
status = btrieveFile->IndexCreate(&btrieveIndexAttributes);

The index segment is defined as a field starting at offset 0 in the record and is 8 bytes long. The data in those 8 bytes will be interpreted as a Btrieve TIMESTAMP.

The AddKeySegment() method appends the btrieveKeySegment instance to a btrieveIndexAttributes object to define a single-segment key.

The SetModifiable() method designates the index as either modifiable (true) or nonmodifiable (false). Other attributes can be used to make the index unique or specify a particular index number.

The IndexCreate() method will add index 0 to the previously opened data file associated with the BtrieveFile object. The index will be populated with all values currently in the file and will be updated automatically on all subsequent insert/update/delete operations.

Reading Records

After creating indexes, retrieving a record is straightforward. There are methods for retrieving the first or last record according to the index definition or retrieving a particular record by comparing to a provided value. In our example, we are going to retrieve the record with the highest time stamp value, which should correspond to the record we just inserted.

Record retrieval methods do not return a status code like other calls we have seen previously. Instead, the size of the retrieved record is returned by the function call. If the size is not returned as expected, then the GetLastStatusCode() method can be called to find out what happened.

Btrieve::StatusCode status = Btrieve::STATUS_CODE_NO_ERROR; 
BPrecord_t record;
// Retrieve last inserted record
if (btrieveFile->RecordRetrieveLast(Btrieve::INDEX_1, 
    (char*)& record, sizeof(record),
     Btrieve::LOCK_MODE_NONE) != sizeof(record))
{
     status = btrieveFile->GetLastStatusCode();
     printf("Error: BtrieveFile::RecordRetrieve():%d:%s.\n", status,
              Btrieve::StatusCodeToString(status));
}

The last argument to the record retrieval methods provides an option to lock the record while retrieving it.

Once you have retrieved a record, you may decide to update or delete it. You cannot update/delete a record without retrieving it first. The btrieveFile->RecordUpdate() and btrieveFile->RecordDelete() methods are used for these operations.

Clean Up

Part of being a responsible programming citizen is ensuring that your programs clean up after themselves. When you are finished with a file, close it by calling the FileClose() method on your btrieveClient object.

status = btrieveClient->FileClose(btrieveFile));

Though you probably won’t need it very often, Btrieve 2 even provides a method for deleting a data file.

status = btrieveClient->FileDelete(btrieveFileName);

Before closing your application, you should release your session with the engine by calling the Reset() method.

status = btrieveClient->Reset();

Additional Btrieve 2 API documentation and examples, including the one in this tutorial, are available online.