INF: Programming DB-LIBRARY TSR with MS-DOS Extenders

ID Number: Q75295

1.10 1.11 4.20

OS/2

Summary:

The MS-DOS DB-LIBRARY (db-lib) versions 1.1 and later use a

terminate-and-stay-resident (TSR) utility to interface with the

network. This utility, written to the OpenNet specification, is the

connection between db-lib and whatever network the TSR is coded to

communicate with. Because this utility is a TSR, interrupts are

required to allow code execution paths from db-lib to the TSR. When

the MS-DOS Extenders are used in protected mode, the interrupt driver

scheme in db-lib fails to correctly interface with the TSR in real

mode. This document is designed to provide enough information to the

db-lib application developer to correctly call the TSR from protected

mode to real mode.

More Information:

The TSR consists of eight C routines managed by an assembly language

interrupt manager. The eight routines are accessed via INT 62H, with

AH containing the index to the requested function. The indexes are as

follows:

0 - ConnectionObjectSize

1 - ConnectionOpen

2 - ConnectionRead

3 - ConnectionWrite

4 - ConnectionTransact

5 - ConnectionWriteOOB

6 - ConnectionClose

7 - ConnectionCheckForData

The interrupt manager in the TSR examines AH for the index. To verify

that INT 62H is targeted to the TSR, the DX value must be -1 and the

CX value must be -2. If any of these values are not correct, the TSR

calls through the original INT 62H vector.

When the db-lib interrupt handler is called by the low-level db-lib

routines that want to communicate with the TSR, a series of actions

occur. The parameters for the TSR functions are pushed on the stack in

the same order as listed below, without the INTRMAP but with the

addition of the index value for example, in a call that ultimately

would be processed by ConnectionTransact():

short API

ConnectionTransact(map,ConnectionObject,readbuffer,writebuffer,writeco

unt,readcount,readmax,TimeOut,neterrno)

void *ConnectionObject;

unsigned char *readbuffer;

unsigned char *writebuffer;

unsigned short writecount;

unsigned short readcount;

unsigned short readmax;

short TimeOut;

short *neterrno;

The following push sequence occurs:

push word (2 bytes: this is the index to the TSR function)

push far void pointer (4 bytes: ConnectionObject)

push far unsigned char pointer (4 bytes: readbuffer)

push far unsigned char pointer (4 bytes: writebuffer)

push word (2 bytes: writecount)

push word (2 bytes: readcount)

push word (2 bytes: readmax)

push word (2 bytes: TimeOut)

push short far pointer (4 bytes: neterrno)

Once in the interrupt handler, the above code pushes BP, SI, and DI to

save local registers in the usual C function stack frame setup. The

db-lib interrupt manager then examines the index to make sure it is in

range, then sets CX and DX to the verification values -2 and -1,

respectively. An INT 62H is then executed. This results in the flags

register being pushed and CS:IP. The TSR now has control. The index

value is verified, and the verification values in CX and DX are then

checked. The TSR then pushes DS to preserve the application's data

segment, then it loads the DGROUP segment for the TSR into DS. The TSR

now calls through a jump table to the C routine requested. When the C

routine receives control, the INTRMAP is pushed on the stack as shown

in the prototypes. On return from the C routine to the interrupt

manager in the TSR, the application's DS is popped from the stack and

an IRET is executed.

The INTRMAP and TSR function declarations are listed below. Note that

ALL pointers are far, and that the TSR is compiled in large model and

expects only far pointers (after all, you cannot do an interprocess

call and expect to use near pointers).

API defines to: cdecl far

The INTRMAP structure represents the state of the stack when the

function receives control:

/* This structure defines the extra bytes pushed on the stack by the

** series of calls to get from an application to the TSR entry point

** for each C function. These bytes must be accounted for in order for

** the function to correctly index into the stack to get its parameters.

** Remember, this module is ALWAYS compiled in LARGE model, and all

** pointers passed to it are FAR.

*/

typedef struct intrmap

{

int intr_man_ds; /* interrupt manager DS save

*/

void far *intr_return; /* interrupt return address

*/

int intr_flags; /* flags

*/

int intr_call_si; /* save of SI by interrupt caller

*/

int intr_call_di; /* save of DI by interrupt caller

*/

int intr_call_bp; /* save of BP by interrupt caller

*/

void far *intr_call_ret; /* return address of interrupt caller

*/

int index;

} INTRMAP;

unsigned short API ConnectionRead(map,ConnectionObject,

buffer,readsize,MaxSize,TimeOut,neterrno)

INTRMAP map;

void *ConnectionObject;

unsigned char *buffer; // 512 bytes long

unsigned short readsize;

unsigned short MaxSize;

unsigned short TimeOut;

unsigned short *neterrno;

short API

ConnectionWrite(map,ConnectionObject,buffer,Writecount,neterrno)

INTRMAP map;

void *ConnectionObject;

unsigned char *buffer; // 512 bytes long

short Writecount;

short *neterrno;

short API

ConnectionTransact(map,ConnectionObject,readbuffer,writebuffer,writeco

unt,readcount,readmax,TimeOut,neterrno)

INTRMAP map;

void *ConnectionObject;

unsigned char *readbuffer; // 512 bytes long

unsigned char *writebuffer; // 512 bytes long

unsigned short writecount;

unsigned short readcount;

unsigned short readmax;

short TimeOut;

short *neterrno;

short API ConnectionWriteOOB(map,ConnectionObject, buffer,

OOBsize,neterrno)

INTRMAP map;

void *ConnectionObject;

unsigned char *buffer; // OOBsize bytes long

short OOBsize;

short *neterrno;

short API ConnectionOpen(map,ConnectionObject,ServerName,neterrno)

INTRMAP map;

void *ConnectionObject;

char *ServerName; // null terminated string, variable length

short *neterrno;

short API ConnectionClose(map,ConnectionObject,neterrno)

INTRMAP map;

void *ConnectionObject;

short *neterrno;

short API

ConnectionCheckForData(map,ConnectionObject,bytesavail,neterrno)

INTRMAP map;

void *ConnectionObject;

long *bytesavail; /* pointer to buffer for number of bytes

available */

short *neterrno;

All return values are returned in AX.

For an MS-DOS Extender to operate with this TSR, several conditions

must be met:

- The buffers that are passed to the TSR MUST be in real mode and

all other pointers to buffers MUST be in real mode (such as the

connection object pointer buffer).

- The application can call the function ConnectionObjectSize() by

calling the routine interrupt_call(0) or can intercept the return

from that call when dbopen() initiates it and intercept the return

value to store it. The return value in AX is the size of the

connection object.

- The interrupt_call() routine is the db-lib interrupt manager

handler [0 is the index for the ConnectionObjectSize() routine]

and is an assembly routine with a C entry convention. To map the

connection object to real mode, you have to determine its size in

program start, then allocate a real mode buffer whenever a call

to any of these routines occurs [except ConnectionObjectSize()

itself] and copy that connection object to the real mode buffer.

- All other references by a pointer must be mapped to real mode.

- To correctly intercept the interrupt manager calls, the Extender

application should grab the INT 62H at the start of the program,

remapping that vector to another unused vector.

- The location the new vector points to MUST contain the string

"DBLIBRARY" after a jmp near instruction. This string in the TSR

code is used to determine the presence of the TSR. Inside the

TSR's entry point for the interrupt is the following code:

jmp short start ; jump around signature

db 'DBLIBRARY' ; signature

start:

... ; execution code

If the above string is not present, on any call to dbopen() [or

dbinit()], dbopen() will post an error that the TSR is not loaded.

This occurs only if the application was substituting a new INT 62H

vector for the DBNMPIPE.EXE vector. With the interrupt substitution

completed, your application would receive control with the stack set

up as shown on entry to the TSR's interrupt manager. It is the same

register configuration as on entry to each TSR function, except that

DS has not been pushed; therefore, to have the stack set up, remove

the intr_man_ds function declaration from the INTRMAP structure.

Now the Extender routine has to map the buffers into real mode, using

a buffer size as indicated for each function. Push these onto a real

mode stack as indicated for the requested function. The index value

can be examined from the stack to determine what the current stack

configuration is. Then, switch to real mode and do the new INT,

whatever that may be. Upon return to the Extender routine, the real

mode buffers have to be remapped to protected mode, along with any

other values set into pointer buffers (neterrno, and so on). Switch

out of real mode and IRET to db-lib. Protected mode is now

successfully integrated into real mode. Once in real mode in the TSR,

the network redirector calls execute correctly, because the switch to

real mode has already been accomplished.

Upon exit from the Extender program, the INT 62H vector MUST be

restored to allow other applications to use the TSR and to allow

ENDDBLIB.EXE to unload the TSR.

Additional reference words: dblib