MIXING REAL-AND PROTECTED-MODE CODE

Using an intermode call buffer is one technique for moving data between real and protected mode

Kerry Loynd

Kerry is a senior programmer at M & R Services Inc. He can be contacted at 1301 5th Ave., Suite 3600, Seattle, WA 98101.


You're probably already saying "Ugh, even if I can mix real-and protected-mode code, why would I want to?" Well, there are times when it makes good sense. Where I work, for example, we recently developed a mathematical tool for actuaries that manipulates large amounts of data, anywhere from many hundreds of kilobytes to several megabytes. The only sensible way to deal with such large amounts of information was to take advantage of protected mode's large address space. But (there's always that "but") some of our files were in Novell's Btrieve format, and Btrieve, as of this writing, doesn't come in a DOS-usable, protected-mode version.

Our alternatives were to redo the code with another file manager that could run in protected mode, or stay with Btrieve and mix addressing modes. Several products on the market include source code that runs under protected mode, and if we started again, I would certainly examine that option. But because time was short and we wanted to minimize our effort, we chose to mix modes.

If you think about it, you can probably come up with similar scenarios of your own. What if, for example, you have a highly specialized math library, and you have only the real-mode object code, or you have a special-purpose device attached to your computer that uses a real-mode device driver. Mixing real-and protected-mode code is a useful technique you could apply in both cases.

Moving Data Between Real and Protected Mode

It's no secret that real- and protected-mode addresses are very different beasties. Real-mode addresses correspond directly to hardware addresses; protected-mode addresses are logical addresses that don't map directly to hardware addresses. They have to be decoded by the processor's memory-management circuitry.

Because there is no direct correlation between real- and protected-mode addresses, how do you move data between modes? You could (and sometimes should) put the values you need into registers and switch modes. It's fast, it's simple, but it's only good for very small amounts of data, and you would probably have to build an assembly language routine to do it.

Another alternative would be to write what you need to disk and let DOS and the extender slug it out. You could certainly handle large quantities of information, but the performance would be excruciatingly slow.

A third method, and the focus of this article, is to use an intermode call buffer. This is an area of memory guaranteed to be accessible to both real- and protected-mode addressing. You get speedy access to your data, and you can declare enough space to access realistic amounts of information.

How a Call Buffer Works

The first requirement for a call buffer is that it has to be accessible in real mode. That means it has to live in the first megabyte of memory. (Sorry, no silver bullets here.) The next requirement is to make sure there is an entry in the protected-mode descriptor table that defines a segment of memory with the same length, at the same physical address. You don't have to worry about the details of this unless you really like to get under the hood. The DOS extender will take care of allocating the call buffer space and mapping it to protected-mode space. You determine the size of the call buffer with command-line switches for the extender or with link options.

How do you get the address information on the call buffer? You have to ask the extender to give it to you. There are two ways to do that: The first is to link in the extender's Application Program Interface (API) library and simply make a function call from your protected-mode program. Because the extender starts your program in protected mode, that's easy to do: See APIBTRV.C (Listing One, page 82).

Another way to get the scoop on the call buffer is to issue a software interrupt directly to the extender. That avoids the use of an outside library, but takes a little more effort to figure out and make work. Then you have to dig through the extender documentation to get the particulars for each call you need. I don't know about you, but to me int386() calls are harder to read in the source code than function names. Anyway, 386BTRV.C shows how to use Watcom's int386() to make the calls to the extender.

Real-Mode Switching and Executing

Protected-mode programs can issue real-mode interrupts and call real-mode procedures. Real-mode programs can call protected-mode procedures. Though this article focuses on the former, the accompanying text box entitled, "The Flip side of the Coin: Going from Real to Protected Mode," provides guidelines for the latter.

DOS extenders use DOS interrupt calls to invoke the functions you need. For example, Phar Lap uses DOS interrupt 0x21, function 0x25. You specify a subfunction code for the extender function you need. The functions take values you specify in a parameter block, switch to real mode, and start execution of the interrupt or procedure you asked for. When the real-mode code returns, these functions clean up and switch back to protected mode.

A Sample Application

The sample application is a card file program; see Listing Two (page 84). Although not fancy, it does illustrate all the concepts I've covered. There are two ways to build the application, depending on which Btrieve interface file you use. The 386BTRV.C file (Listing Three, page 86) calls the extender with int386(). APIBTRV.C uses Phar Lap's API library calls from DOSX32.LIB. These interface files were built for Btrieve for DOS, Version 5.10a. They might run with earlier versions of Btrieve, but I haven't tried. The code was compiled with Watcom C, Version 8.0, and targets PharLap's 386|DOS-Extender, Version 3.0. Instructions on how to compile and run the application are in the file headers for APIBTRV.C and 386BTRV.C. Listing Four (page 88) is BTRV_DEF.H, and contains the manifest constants used to interface with Btrieve and the structures used to set up Btrieve files. Listing Five (page 90) is BTRVERRS.H, the file that contains the error codes returned by Btrieve.

More Details.

When the program starts, a menu will come up allowing you to Find, Create, or List records, or Exit. Find asks for the name to find and displays the record alphabetically greater than or equal to the name you entered. Then it asks if you want to edit the record. Create allows a record to be entered and tells whether it was successful or whether a matching record was already in the file. List summarizes all the records in the file in alphabetic order. Exit closes the file and terminates the program. Please note that the source code shows the Btrieve file being opened in accelerated mode. If your program crashes or stops executing without going through the Exit option, the data in the file will probably be trashed.

The main points of interest for this article are in the Btrieve interface files. These are modeled after the interface files Novell supplies with Btrieve. They take care of setting up and executing the interrupt that requests Btrieve services. The first thing they do is make sure Btrieve is loaded before they issue the interrupt to Btrieve. The Btrieve TSR can be detected by looking at the offset part of its real-mode interrupt vector. APIBTRV gets the vector by calling DX_RMIV_GET(). BTR_INT is the number of the interrupt vector to load, and realAddr is where the vector is returned. The code in 386BTRV builds a register overlay by putting BTR_INT into CL, and 0x2503 into AX. The 0x25 is the extender function code, and 0x03 is a request to fetch a real-mode interrupt vector. After calling DOS via int386(), the vector is in EBX.

If Btrieve is loaded, the next step is to get the intermode call buffer information. APIBTRV calls DX_RMLINK_GET() and gets the buffer's real-mode address in realAddr, the buffer size (in bytes) in buffSize, and a protected-mode far pointer to the buffer in xbuff. The size can be useful, but it's not used in this application. DOS386RealProc gets the address of a real-mode entry point that allows your real-mode code to call back to your protected-mode procedures.

386BTRV gets the current segment register values and puts 0x250D in AX. 0x0D is a request for the vitals on the call buffer. The protected-mode far address is returned in ES:EDX, and that address is placed in xbuff. The real-mode address comes back in EBX, and the size, in bytes, is in ECX. EAX holds the real-mode to protected-mode entry point.

In both cases, the real-mode address is broken into its segment and offset components. The Btrieve file control block, the key buffer, and the data are copied into the call buffer through the structure laid out by xbuff, and the Btrieve parameter block is loaded with the operation code and the real-mode addresses of those copies.

Once the call buffer has been loaded, both interfaces put the Btrieve interrupt number and the real-mode address of the call buffer into a parameter block. APIBTRV then calls DX_REAL_INT with the protected-mode address of the parameter block (not the call buffer). This call sets up the machine registers, switches to real mode, executes the interrupt, then cleans up and returns to APIBTRV. 386BTRV, on the other hand, sets up the register overlay with 0x2511 in AX (0x11 is a request to issue a real-mode interrupt with registers specified), the protected-mode selector of the parameter block (not the call buffer) in DS, and its offset in EDX. Then it calls int386x() to execute the Btrieve request.

When the Btrieve interrupts return, both interfaces copy the data returned by Btrieve from the call buffer back to the application's protected-mode space. The Btrieve status code is returned to whichever routine called BTRV. Nothing to it, eh?

Mixing real-and protected-mode code may not be the most intuitive thing in the world, but it is fairly simple. If you run into trouble, I have found the DOS extender vendors knowledgeable and helpful. Next time you have a memory constraint problem, take a look at mixing modes. You just might find a good solution to your problem.

The Flip Side of the Coin: Going from Real to Protected Mode

In addition to allowing protected-mode programs to invoke real-mode code, DOS extenders give you a means of calling protected-mode code from real mode. When your protected-mode code calls the extender to get the information about the intermode call buffer, one of the parameters returned to you is a real-mode far pointer to a function that you can use to call a protected-mode procedure.

To use this entry point, both your real-mode and protected-mode code must already be loaded. The protected-mode code has to pass a protected-mode far pointer to the real-mode code. The best way to do this is to put the pointer into the call buffer along with the address of the real-to-protected-mode entry point. The real-mode code can then get the entry point address from the call buffer and put it in a far function pointer variable. Push the parameters onto the stack just as for a normal function call. If you are using the Watcom compiler, remember to declare your protected-mode function with the cdecl keyword so it will expect the parameters on the stack. Then push either a long word 0 or a real-mode far pointer to a block holding protected-mode segment register values for DS, ES, FS, and GS. Finally, put the protected-mode address of the procedure to execute on the stack. Your call should look something like this: (*rmToPmEntryPoint) (pmFunctionOffset, pmFunctionSelector, &segRegBlock, parm1, parm2, parm3);. Remember, protected-mode far pointers have 16-bit selectors and 32-bit offsets.

  The segRegBlock is shaped like this:

  struct {
      short DS;
      short ES;
      short FS;
      short GS;
  } sRegStruct;

If you give the address of such a block, the protected-mode code will use the values in that block for its selector values. If you push 0L, it will use the values those registers had when the protected-mode code first started.

The real-to-protected entry code takes the protected-mode address and the selector values block from the stack. A protected-mode return address is put on the stack, and the stack is adjusted. The protected-mode function will only see the parameters you want it to use. When you declare the protected-mode function, be sure to use the far keyword. You have to do this because the routine has to execute a far return to get back to real mode with the stack correctly aligned. After returning to real mode, the segment register parameter block will have the values that were in the segment registers when the protected-mode function returned. The stack will have zero where the protected routine address and the segment register block address were stored.

There is one nonobvious point to look out for when calling from real to protected mode. When control is given to the protected-mode routine, it is using the same physical stack space as the real-mode code. If you want to use library routines that were compiled with stack checking, you'll have to set the protected-mode stack to the stack segment in use when the protected code started. Otherwise you'll get memory protection violations. Be sure to reset the stack to the real-mode segment before returning to real mode.

--K.L.


_MIXING REAL- AND PROTECTED-MODE CODE_
by Kerry Loynd



[LISTING ONE]



/*********************************************************************
 * Filename.......  APIBTRV.C  Version........  1.2
 * Author.........  Kerry Loynd
 * Comments.......  WatCom C interface to the Btrieve Record Manager
 This particular incarnation of the BTRV call interface is for the 32-bit
 Watcom compiler, and uses int386() to generate calls to BTRIEVE from programs
 operating in protected mode under Pharlap's 386|DOS. To compile it use
 the command line
        C> WCL386 switcher.c apibtrv.c -l=<pharlap path>dosx32
 To run the application use
        C> BTRIEVE
        C> RUN386 -callbuf n switcher <btrFile>
 where n is size, in KBytes, of the intermode call buffer. The size of that
 buffer must be greater than or equal to the size of the XBUFFER struct plus
 the maximum data record size used by your program. BtrFile is the name of the
 data file you want to use with the application. Use 1 for this application.
    There is a subtle point here that you must watch. ALWAYS pass all the
 defined parameters to a call to BTRV. Be especially careful to set *dataLen
 to 0 if it is not used in the operation you are performing. Otherwise, you
 may find that your program has gone over the wall...
    I could have placed a check for an oversize dataLen and returned a status
 code, but, since I can't guarantee whether Novell will use the same status
 code some time in the future, I did not. NOTE: You should be aware that
 Watcom's linker will not successfully search DOSX32.LIB unless you use
 Watcom's librarian to extract all the object modules and then re-insert them
 into the library. If you want to understand why, call Watcom or Pharlap.
 * COPYRIGHT (C) 1991.  All Rights Reserved.
 * Kerry Loynd  Seattle, WA
 ********************************************************************/

#include <stddef.h>
#include <dos.h>
#include <string.h>

typedef unsigned short int INT;
typedef unsigned long int UINT;
typedef unsigned long ULONG;
typedef unsigned long REAL_ADDR; /* used for real-mode addresses. */
typedef unsigned char UCHAR;     /* unsigned 8-bit value */
typedef UCHAR far * FARPTR;

#define SINT short
#define BTR_ERR     20      /* record manager not started */
#define BTR_INT     0x7B    /* Btrieve interrupt vector */
#define BTR_OFFSET  0x33    /* Btrieve offset within segment */
#define VARIABLE_ID 0x6176  /* id for variable length records */

/* INT_BLOCK is a structure used by 386|DOS to invoke real mode interrupts. */
typedef struct intblock {
    SINT intNumber;          /* Interrupt to invoke. */
    SINT rds;                /* real mode ds. */
    SINT res;                /* real mode es. */
    SINT rfs;                /* real mode fs. */
    SINT rgs;                /* real mode gs. */
    unsigned long reax;      /* real mode eax. */
    unsigned long redx;      /* real mode edx. */
} INT_BLOCK;

/* Whatever compiler you use, this structure must be byte aligned. */
typedef struct BTRIEVE_PARMS /* structure passed to Btrieve Rec Mgr */
 {
   REAL_ADDR bufAddress;  /* caller's data buffer real mode address */
   INT       bufLength;   /* length of data buffer */
   REAL_ADDR curAddress;  /* user position block real mode address */
   REAL_ADDR fcbAddress;  /* Real mode address of disk FCB */
   INT       function;    /* requested function */
   REAL_ADDR keyAddress;  /* Real mode address of user's key buffer */
   char      keyLength;   /* length of user's key buffer */
   char      keyNumber;   /* key of reference for request */
   REAL_ADDR statAddress; /* Real mode address of status word */
   INT       xfaceID;     /* language identifier */
 } BTR_PARMS;
typedef struct xbuffer {     /* Structure of intermode call buffer. */
    BTR_PARMS xData;         /* Btrieve parameter block. */
    INT stat;                /* status of Btrieve call */
    char posBlock[128];      /* Btrieve file control block */
    char keyBuff[255];       /* key buffer for this Btrieve file. */
    char dataBuffer[1];      /* Data buffer space. */
} XBUFFER;

// The following function prototypes are copied from pharlap.h. They have to
// be declared with cdecl because they expect their parameters on the stack.
extern int cdecl DX_RMIV_GET(UINT, REAL_ADDR *);
extern int cdecl DX_REAL_INT(INT_BLOCK *);
extern int cdecl DX_RMLINK_GET(REAL_ADDR *, REAL_ADDR *, ULONG *, FARPTR *);

SINT cdecl BTRV (SINT operation,
           UCHAR *posBlock,
           char *dataBuf,
           INT  *dataLen,
           char *keyBuf,
           SINT  keyNum
          )
{
XBUFFER far *xbuff;
SINT realSeg;
SINT realOffset;
REAL_ADDR realAddr;
REAL_ADDR DOS386RealProc;
ULONG buffSize;
INT_BLOCK callBlock;

/*  Check to see that the Btrieve Record Manager has been started.  */
  DX_RMIV_GET (BTR_INT, &realAddr);  /* Get real-mode int vector.   */
  if ((realAddr & 0xFFFF) != BTR_OFFSET)  /* is Btrieve installed?  */
     return (BTR_ERR);
/* Get the real and protected addresses of the call buffer. */
/* DOS386RealProc and buffSize are not used.                */
 DX_RMLINK_GET (&DOS386RealProc, &realAddr, &buffSize,
      (FARPTR *)&xbuff);
  realSeg = (realAddr >> 16) & 0xFFFF; /* real-mode segment. */
  realOffset = realAddr & 0xFFFF;      /* real-mode offset.  */
/* Get the key and data info from the caller. */
  _fmemcpy (xbuff->posBlock, (char far *)posBlock, 128);
  _fmemcpy (xbuff->keyBuff, (char far *)keyBuf, 255);
  _fmemcpy (xbuff->dataBuffer, (char far *)dataBuf, *dataLen);
/*  Move user parameters to xbuff, where Btrieve can find them.     */
  xbuff->stat = 0;
  xbuff->xData.function    = operation;
  xbuff->xData.statAddress = realAddr + offsetof (XBUFFER,stat);
  xbuff->xData.fcbAddress  = realAddr + offsetof (XBUFFER,posBlock);
  xbuff->xData.curAddress  = xbuff->xData.fcbAddress + 38;
  xbuff->xData.bufAddress  = realAddr + offsetof (XBUFFER,dataBuffer);
  xbuff->xData.bufLength   = *dataLen;
  xbuff->xData.keyAddress  = realAddr + offsetof (XBUFFER, keyBuff);
  xbuff->xData.keyLength   = 255;  /* use max since we don't know */
  xbuff->xData.keyNumber   = keyNum;
  xbuff->xData.xfaceID     = VARIABLE_ID;

/*  Make call to the Btrieve Record Manager.                        */
/* Set up for Extended DOS call. Put real-mode interrupt call data into
the call block. */
  callBlock.intNumber = BTR_INT;
  callBlock.rds = realSeg;
  callBlock.redx = realOffset;
  DX_REAL_INT (&callBlock);  /* Issue real mode int, regs specified.*/
  *dataLen = xbuff->xData.bufLength;
/* Copy the key and data info back to the caller. */
  _fmemcpy ((char far *)posBlock, xbuff->posBlock, 128);
  _fmemcpy ((char far *)keyBuf, xbuff->keyBuff, 255);
  _fmemcpy ((char far *)dataBuf, xbuff->dataBuffer, *dataLen);
  return (xbuff->stat);   /* return status */
}




[LISTING TWO]


// Switcher.c
// This code should be easily portable to compilers other than Watcom's

#include <stdio.h>
#include <stddef.h>
#include <process.h>
#include <ctype.h>
#include <string.h>
#include "btrv_def.h"
#include "btrverrs.h"

#define SINT short
typedef unsigned char UCHAR;    /* unsigned 8-bit value */

extern SINT cdecl BTRV (int, UCHAR *, char *, int *, char *, int);

/* CREATE_STRUCT is used to create the card file. */
typedef struct tagCREATE_STRUCT {
    FILE_SPEC fileInfo;
    KEY_SPEC keyInfo;
} CREATE_STRUCT;
/* CARD_STRUCT is the structure of the card file records */
typedef struct tagCARD_STRUCT {
    char name[21];
    char address[21];
    char city[21];
} CARD_STRUCT;
#define CARD_LEN sizeof (CARD_STRUCT)
#define NAME_KEY 0
static unsigned char posBlock[128];  /* Btrieve's position control */
static char searchKey[255]; /* Search key buffer. MUST be 255 long.*/
static CREATE_STRUCT cardInfo = {
    sizeof(CARD_STRUCT), 1024, 1, 0L, NO_FILE_FLAGS, 0, 0,
    1, 21, EXTENDED_TYPE_KEY | MODIFIABLE_KEY, 1L, ZSTRING_KEY, 0, 0, 0, 0, 0
};
static void EditRecord (CARD_STRUCT *card)
{
    char done = 'n';
    char gunkCatcher[80];
    while (done != 'y') {
        gets(gunkCatcher);  /* Flush out the input buffer. */
        printf ("\n\nName: %s\n:", card->name);
        flushall ();
        gets (card->name);
        printf ("\nAddress: %s\n:", card->address);
        flushall ();
        gets (card->address);
        printf ("\nCity: %s\n:", card->city);
        flushall ();
        gets (card->city);
        printf ("\n%-21s, %-21s, %-21s\n", card->name,
          card->address, card->city);
        printf ("\n\nDone? [y/n] ");
        flushall ();
        done = tolower (getchar());
        printf ("Done = |%c|\n", done);
    }
}
static void CreateRecord (void)
{
    CARD_STRUCT card;
    int rc = 0;
    int len = CARD_LEN;
    memset (&card, '\0', sizeof (CARD_STRUCT));
    EditRecord (&card);
    rc = BTRV (INSERT_BTR, &posBlock[0], (char *)&card, &len,
        searchKey, NAME_KEY);
    if (rc != 0)
    printf ("Could not insert record %s, BTRV error %d\n",
        card.name, rc);
}
static void FindRecord (void)
{
    CARD_STRUCT card;
    int rc = 0;
    int len = CARD_LEN;
    gets (searchKey); /* Flush out the input buffer. */
    printf ("Name to search on: ");
    flushall ();
    gets (searchKey);
    rc = BTRV (GET_GT_EQ, &posBlock[0], (char *)&card, &len,
        searchKey, NAME_KEY);
    if (rc == 0) {
        printf ("Found %s.\n\nEdit this record?", searchKey);
        if (tolower(getchar ()) == 'y') {
            EditRecord (&card);
            rc = BTRV (UPDATE_BTR, &posBlock[0], (char *)&card, &len,
                searchKey, NAME_KEY);
            if (rc != 0)
                printf ("Could not insert record %s, BTRV error %d\n",
                    card.name, rc);
        }
    } else
        printf ("Could not find a record.\n");
}
static void ListRecords ()
{
    CARD_STRUCT card;
    int rc = 0;
    int len = CARD_LEN;
    printf ("\n\nList of records:\n\n");
    rc = BTRV (GET_FIRST, &posBlock[0], (char *)&card, &len,
        searchKey, NAME_KEY);
    while (rc == 0) {
        printf ("%-21s, %-21s, %-21s\n", card.name, card.address,
            card.city);
        len = CARD_LEN;
        rc = BTRV (GET_GT, &posBlock[0], (char *)&card, &len,
            searchKey, NAME_KEY);
    }
    if (rc != END_OF_FILE_BTR)
    printf ("\n\nBTRV error %d\n", rc);
    printf ("Press any key to continue.\n");
    while (!kbhit()); /* wait for a key */
    getch (); /* clean out the dregs. */
}
int main (int argc, char **argv)
{
    int rc = 0;
    int menuChoice = 0;
    int len = 0;
    char fakeData[1];
    if (argc < 2) {
        printf ("No card file specified.\n");
        exit(1);
    }
    strcpy (searchKey, argv[1]);
    rc = BTRV (OPEN_BTR, &posBlock[0], fakeData, &len, searchKey,
        ACCELERATED);
    if (rc == BTRIEVE_INACTIVE_BTR) {
        printf ("Btrieve isn't loaded.\n");
        exit(1);
    }
    if (rc !=  0) {
        len = sizeof(CREATE_STRUCT);
        rc = BTRV (CREATE_BTR, &posBlock[0], (char *)&cardInfo, &len,
            searchKey, NAME_KEY);
        if (rc != 0) {
            printf ("BTRV create returned %d\n", rc);
            BTRV( STOP_BTR, &posBlock[0], fakeData, &len, fakeData,
                NAME_KEY);
            exit (1);
        }
        len = 0;
        rc = BTRV (OPEN_BTR, &posBlock[0], fakeData, &len, searchKey,
            ACCELERATED);
        if (rc != 0) {
            printf ("BTRV open returned %d\n", rc);
            BTRV( STOP_BTR, &posBlock[0], fakeData, &len, fakeData, NAME_KEY);
            exit (1);
        }
    }
    menuChoice = 0;
    while (menuChoice != 9) {
        printf ("\n\n1. Find a record\n");
        printf ("2. Create a new record\n");
        printf ("3. List records\n");
        printf ("9. Exit\n\n:");
        scanf ("%d", &menuChoice);
        switch (menuChoice) {
            case 1:
                FindRecord ();
                break;
            case 2:
                CreateRecord ();
                break;
            case 3:
                ListRecords ();
                break;
        }
    }
    BTRV (CLOSE_BTR, &posBlock[0], fakeData, &len, searchKey,
        NAME_KEY);
    BTRV( STOP_BTR, &posBlock[0], fakeData, &len, searchKey,
        NAME_KEY);
    return 0;
}




[LISTING THREE]




/*********************************************************************
 * Filename.......  386BTRV.C
 * Version........  1.2
 * Version Date...  August 8, 1991
 * Author.........  Kerry Loynd
 * Comments.......  WatCom C interface to the Btrieve Record Manager

    This particular incarnation of the BTRV call interface is for the
    32-bit Watcom compiler, and uses Pharlap API library calls to
    generate calls to BTRIEVE from programs operating in protected
    mode under Pharlap's 386|DOS.  To compile it use the command line

        C> WCL386 switcher.c apibtrv.c -l=<pharlap path>dosx32

    To run the application use

        C> BTRIEVE
        C> RUN386 switcher <btrFile>

    where n is the size, in KBytes, of the intermode call buffer.  The
    size of that buffer must be greater than or equal to the size of
    the XBUFFER struct plus the maximum data record size used by your
    program.  BtrFile is the name of the data file you want to use
    with the application.  Use 1 for this application.

    There is a subtle point here that you must watch.  ALWAYS pass all
    the defined parameters to a call to BTRV.  Be especially careful
    to set *dataLen to 0 if it is not used in the operation you are
    performing.  Otherwise, you may find that your program has gone
    over the wall...

    I could have placed a check for an oversize dataLen and returned
    a status code, but, since I can't guarantee whether Novell will
    use the same status code some time in the future, I did not.

    NOTE: You should be aware that Watcom's linker will not
    successfully search DOSX32.LIB unless you use Watcom's librarian
    to extract all the object modules and then re-insert them into the
    library.  If you want to understand why, call Watcom or Pharlap.

 *
 * COPYRIGHT (C) 1991.  All Rights Reserved.
 * Kerry Loynd  Seattle, WA
 * (206)624-7970
 ********************************************************************/

#include <stddef.h>
#include <dos.h>
#include <string.h>

typedef unsigned short int INT;
typedef unsigned long int UINT;
typedef unsigned long ULONG;
typedef unsigned long REAL_ADDR; /* used for real-mode addresses. */
typedef unsigned char UCHAR;     /* unsigned 8-bit value */
typedef UCHAR far * FARPTR;

#define SINT short
#define BTR_ERR     20      /* record manager not started */
#define BTR_INT     0x7B    /* Btrieve interrupt vector */
#define BTR_OFFSET  0x33    /* Btrieve offset within segment */
#define VARIABLE_ID 0x6176  /* id for variable length records */

/*
   INT_BLOCK is a structure used by 386|DOS to invoke real mode
   interrupts.
*/
typedef struct intblock {
    SINT intNumber;          /* Interrupt to invoke. */
    SINT rds;                /* real mode ds. */
    SINT res;                /* real mode es. */
    SINT rfs;                /* real mode fs. */
    SINT rgs;                /* real mode gs. */
    unsigned long reax;      /* real mode eax. */
    unsigned long redx;      /* real mode edx. */
} INT_BLOCK;


/* Whatever compiler you use, this structure must be byte aligned. */

typedef struct BTRIEVE_PARMS /* structure passed to Btrieve Rec Mgr */
 {
   REAL_ADDR bufAddress;  /* caller's data buffer real mode address */
   INT       bufLength;   /* length of data buffer */
   REAL_ADDR curAddress;  /* user position block real mode address */
   REAL_ADDR fcbAddress;  /* Real mode address of disk FCB */
   INT       function;    /* requested function */
   REAL_ADDR keyAddress;  /* Real mode address of user's key buffer */
   char      keyLength;   /* length of user's key buffer */
   char      keyNumber;   /* key of reference for request */
   REAL_ADDR statAddress; /* Real mode address of status word */
   INT       xfaceID;     /* language identifier */
 } BTR_PARMS;

typedef struct xbuffer {     /* Structure of intermode call buffer. */
    BTR_PARMS xData;         /* Btrieve parameter block. */
    INT stat;                /* status of Btrieve call */
    char posBlock[128];      /* Btrieve file control block */
    char keyBuff[255];       /* key buffer for this Btrieve file. */
    char dataBuffer[1];      /* Data buffer space. */
} XBUFFER;

// The following function prototypes are copied from pharlap.h.
// They have to be declared with cdecl because they expect their
// parameters on the stack.
extern int cdecl DX_RMIV_GET(UINT, REAL_ADDR *);
extern int cdecl DX_REAL_INT(INT_BLOCK *);
extern int cdecl DX_RMLINK_GET(REAL_ADDR *, REAL_ADDR *, ULONG *,
                               FARPTR *);

SINT cdecl BTRV (SINT operation,
           UCHAR *posBlock,
           char *dataBuf,
           INT  *dataLen,
           char *keyBuf,
           SINT  keyNum
          )

{
XBUFFER far *xbuff;
SINT realSeg;
SINT realOffset;
REAL_ADDR realAddr;
REAL_ADDR DOS386RealProc;
ULONG buffSize;
INT_BLOCK callBlock;

/*                                                                  */
/*  Check to see that the Btrieve Record Manager has been started.  */
/*                                                                  */
  DX_RMIV_GET (BTR_INT, &realAddr);  /* Get real-mode int vector.   */
  if ((realAddr & 0xFFFF) != BTR_OFFSET)  /* is Btrieve installed?  */
     return (BTR_ERR);

/* Get the real and protected addresses of the call buffer. */
/* DOS386RealProc and buffSize are not used.                */
 DX_RMLINK_GET (&DOS386RealProc, &realAddr, &buffSize,
      (FARPTR *)&xbuff);
  realSeg = (realAddr >> 16) & 0xFFFF; /* real-mode segment. */
  realOffset = realAddr & 0xFFFF;      /* real-mode offset.  */

/* Get the key and data info from the caller. */
  _fmemcpy (xbuff->posBlock, (char far *)posBlock, 128);
  _fmemcpy (xbuff->keyBuff, (char far *)keyBuf, 255);
  _fmemcpy (xbuff->dataBuffer, (char far *)dataBuf, *dataLen);

/*                                                                  */
/*  Move user parameters to xbuff, where Btrieve can find them.     */
/*                                                                  */
  xbuff->stat = 0;
  xbuff->xData.function    = operation;
  xbuff->xData.statAddress = realAddr + offsetof (XBUFFER,stat);
  xbuff->xData.fcbAddress  = realAddr + offsetof (XBUFFER,posBlock);
  xbuff->xData.curAddress  = xbuff->xData.fcbAddress + 38;
  xbuff->xData.bufAddress  = realAddr + offsetof (XBUFFER,dataBuffer);
  xbuff->xData.bufLength   = *dataLen;
  xbuff->xData.keyAddress  = realAddr + offsetof (XBUFFER, keyBuff);
  xbuff->xData.keyLength   = 255;  /* use max since we don't know */
  xbuff->xData.keyNumber   = keyNum;
  xbuff->xData.xfaceID     = VARIABLE_ID;

/*                                                                  */
/*  Make call to the Btrieve Record Manager.                        */
/*                                                                  */

/*
   Set up for the Extended DOS call.  Put the real-mode interrupt
   call data into the call block.
*/
  callBlock.intNumber = BTR_INT;
  callBlock.rds = realSeg;
  callBlock.redx = realOffset;
  DX_REAL_INT (&callBlock);  /* Issue real mode int, regs specified.*/

  *dataLen = xbuff->xData.bufLength;

/* Copy the key and data info back to the caller. */
  _fmemcpy ((char far *)posBlock, xbuff->posBlock, 128);
  _fmemcpy ((char far *)keyBuf, xbuff->keyBuff, 255);
  _fmemcpy ((char far *)dataBuf, xbuff->dataBuffer, *dataLen);

  return (xbuff->stat);   /* return status */
}







[LISTING FOUR]

//BTRV_DEF.H
/* This file contains the manifest constants used to interface with Btrieve
 and the structures used to set up Btrieve files. */

#ifndef _BTRV_DEF_H_
#define _BTRV_DEF_H_

#define OPEN_BTR                 0
#define CLOSE_BTR                1
#define INSERT_BTR               2
#define UPDATE_BTR               3
#define DELETE_BTR               4
#define GET_EQUAL                5
#define GET_NEXT                 6
#define GET_PREVIOUS             7
#define GET_GT                   8
#define GET_GT_EQ                9
#define GET_LT                  10
#define GET_LT_EQ               11
#define GET_FIRST               12
#define GET_LAST                13
#define CREATE_BTR              14
#define GET_STATUS              15
#define EXTEND_BTR              16
#define SET_DIRECTORY           17
#define GET_DIRECTORY           18
#define BEGIN_TRANS             19
#define END_TRANS               20
#define ABORT_TRANS             21
#define GET_POSITION            22
#define GET_DIRECT              23
#define STEP_NEXT               24
#define STOP_BTR                25
#define VERSION_BTR             26
#define UNLOCK_BTR              27
#define RESET_BTR               28
#define SET_OWNER               29
#define CLEAR_OWNER             30
#define CREATE_SUPP_INDEX       31
#define DROP_SUPP_INDEX         32
#define STEP_FIRST              33
#define STEP_LAST               34
#define STEP_PREVIOUS           35
#define GET_NEXT_EXTENDED       36
#define GET_PREV_EXTENDED       37
#define STEP_NEXT_EXTENDED      38
#define STEP_PREV_EXTENDED      39
#define INSERT_EXTENDED         40
#define GET_KEY                 50
#define SINGLE_WAIT_LOCK       100
#define MULTIPLE_WAIT_LOCK     300
#define SINGLE_NOWAIT_LOCK     200
#define MULTIPLE_NOWAIT_LOCK   400

/* These are for key number options. */
#define UNACCELERATED    0
#define ACCELERATED     -1
#define READ_ONLY       -2
#define VERIFY          -3

/* Key flag bit definitions */
#define NO_KEY_FLAGS            0x0000
#define DUPLICATE_KEY           0x0001
#define MODIFIABLE_KEY          0x0002
#define BINARY_KEY              0x0004
#define NULL_KEY                0x0008
#define SEGMENTED_KEY           0x0010
#define ALT_SORT_KEY            0x0020
#define DESCENDING_KEY          0x0040
#define SUPPLEMENTAL_KEY        0x0080
#define EXTENDED_TYPE_KEY       0x0100
#define MANUAL_KEY              0x0200

/* Extended key type definition bits */
#define STRING_KEY               0
#define INTEGER_KEY              1
#define FLOAT_KEY                2
#define DATE_KEY                 3
#define TIME_KEY                 4
#define DECIMAL_KEY              5
#define MONEY_KEY                6
#define LOGICAL_KEY              7
#define NUMERIC_KEY              8
#define BFLOAT_KEY               9
#define LSTRING_KEY             10
#define ZSTRING_KEY             11
#define UNSIGNED_BINARY_KEY     14
#define AUTOINCREMENT_KEY       15

/* File attribute bit definitions */
#define NO_FILE_FLAGS           0x00
#define VARIABLE_LENGTH_FILE    0x01
#define TRUNCATE_BLANKS_FILE    0x02
#define PREALLOCATION_FILE      0x04
#define DATA_COMPRESSION_FILE   0x08
#define KEY_ONLY_FILE           0x10
#define FREE_SPACE_10_FILE      0x40
#define FREE_SPACE_20_FILE      0x80
#define FREE_SPACE_30_FILE      0xC0

#define     BTRV_EQ  1
#define     BTRV_GT  2
#define     BTRV_LT  3
#define     BTRV_NE  4
#define     BTRV_GE  5
#define     BTRV_LE  6
#define     BTRV_AND 1
#define     BTRV_OR  2

/* Make sure that the following structures are packed, using whatever mechanism
  your compiler defines. */
#pragma pack(1)

/* Typedefs to define the return data from a stat call. */
typedef struct {
    short int   recordLength;
    short int   pageSize;
    short int   indexCount;
    long  int   recordTotal;
    short int   fileFlags;
    char        reserved[2];
    short int   preAlloc;
} STATS;

/* typedefs needed for CREATE operation */
typedef struct {
    short int   keyPosition;
    short int   keyLength;
    short int   keyFlag;
    long  int   keyTotal;
    char        keyType;
    char        nullValue;
    char        rsvp[4];
} KEY_SPEC;
typedef struct {
    short int   recordLength;
    short int   pageSize;
    short int   indexCount;
    long int    unused;
    short int   fileFlags;
    short int   reserved;
    short int   allocation;
} FILE_SPEC;

/*  typedefs needed for EXTENEDED operations */
typedef struct
    {
    char        dataType;
    short int   fieldLength;
    short int   fieldOffset;
    char        comparisonCode;
    char        andOrExpression;
    union
        {
        int     Offset;
        char    Value[2];
        } field2;
    }
    FILTER_TERM;
typedef struct
    {
    short int   bufferLength;
    char        command[2];
    short int   maxSkip;
    short int   termCount;
    }
    FILTER_HEAD;
typedef struct
    {
    short int   recordCount;
    short int   fieldCount;
    }
    FILTER_TAIL;
typedef struct
    {
    short int   fieldLength;
    short int   fieldOffset;
    }
    FILTER_FIELD;
#endif







[LISTING FIVE]


//BTRVERRS.H
/* This file contains the error codes returned by Btrieve. */

#define SUCCESS_BTR                               0
#define INVALID_OP_BTR                            1
#define IO_ERROR_BTR                              2
#define FILE_NOT_OPEN_BTR                         3
#define KEY_NOT_FOUND_BTR                         4
#define DUPLICATE_KEY_BTR                         5
#define INVALID_KEY_NUM_BTR                       6
#define DIFF_KEY_NUM_BTR                          7
#define INVALID_POSITION_BTR                      8
#define END_OF_FILE_BTR                           9
#define MOD_KEY_ERROR_BTR                        10
#define BAD_FILE_NAME_BTR                        11
#define FILE_NOT_FOUND_BTR                       12
#define EXT_FILE_ERROR_BTR                       13
#define PREIMG_OPEN_ERR_BTR                      14
#define PREIMG_IO_ERR_BTR                        15
#define EXPANSION_ERROR_BTR                      16
#define CLOSE_ERROR_BTR                          17
#define DISK_FULL_BTR                            18
#define UNRECOVERABLE_BTR                        19
#define BTRIEVE_INACTIVE_BTR                     20
#define KEY_BUFF_TOO_SHORT_BTR                   21
#define DATA_BUFF_TOO_SHORT_BTR                  22
#define POS_BLOCK_LEN_ERR_BTR                    23
#define PAGE_SIZE_ERR_BTR                        24
#define CREATE_IO_ERR_BTR                        25
#define KEY_COUNT_ERR_BTR                        26
#define INVALID_KEY_POS_BTR                      27
#define INVALID_REC_LEN_BTR                      28
#define INVALID_KEY_LEN_BTR                      29
#define NOT_BTRIEVE_FILE_BTR                     30
#define ALREADY_EXTENDED_BTR                     31
#define EXTEND_IO_ERR_BTR                        32
#define INVALID_EXT_NAME_BTR                     34
#define DIR_ERROR_BTR                            35
#define TRANSACTION_ERR_BTR                      36
#define ACTIVE_TRANS_BTR                         37
#define TRANSACT_CTL_IO_ERR_BTR                  38
#define END_TRANSACT_ERR_BTR                     39
#define TRANSACT_MAX_FILES_BTR                   40
#define OP_NOT_ALLOWED_BTR                       41
#define INCOMPLETE_ACCEL_ACCESS_BTR              42
#define INVALID_REC_ACCESS_BTR                   43
#define NULL_KEY_PATH_BTR                        44
#define INCONSISTENT_KEY_FLAGS_BTR               45
#define ACCESS_DENIED_BTR                        46
#define MAX_FILES_OPEN_BTR                       47
#define BAD_ALT_DEF_FILE_BTR                     48
#define KEY_TYPE_ERR_BTR                         49
#define OWNER_ALREADY_SET_BTR                    50
#define INVALID_OWNER_BTR                        51
#define CACHE_WRITE_ERR_BTR                      52
#define BAD_INTERFACE_BTR                        53
#define VARIABLE_PAGE_ERR_BTR                    54
#define AUTOINCREMENT_ERR_BTR                    55
#define INCOMPLETE_INDEX_BTR                     56
#define EXP_MEMORY_ERR_BTR                       57
#define SQUEEZE_BUFF_TOO_SHORT_BTR               58
#define FILE_EXISTS_BTR                          59
#define FILTER_LIMIT_REACHED_BTR                 64
#define CONFLICT_BTR                             80
#define LOCK_ERR_BTR                             81
#define LOST_POSITION_BTR                        82
#define READ_OUTSIDE_TRANSACT_BTR                83
#define RECORD_IN_USE_BTR                        84
#define FILE_IN_USE_BTR                          85
#define FILE_TABLE_FULL_BTR                      86
#define HANDLE_TABLE_FULL_BTR                    87
#define BAD_OPEN_MODE_BTR                        88
#define BAD_LOCK_TYPE_BTR                        93
#define PERMISSION_ERR_BTR                       94

Copyright © 1992, Dr. Dobb's Journal