Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

A GLOBAL VARIABLE DEVICE DRIVER FOR MS-DOS


OCT89: A GLOBAL VARIABLE DEVICE DRIVER FOR MS-DOS

Jim Mischel is a former financial systems programmer and database consultant. He can be reached at 20 Stewart St., Durango, CO 81301 or on CompuServe: 73717,1355.


While the MS-DOS environment string setup is a handy way to define system-wide information, it suffers from one major drawback: The inability of a child process to make permanent modifications to an ancestor's environment. Why, one might ask, would you want to do such a thing? I asked the same question the first time somebody brought it up, but as luck would have it, I soon found myself answering it.

I support an application that makes extensive use of environment variables to store configuration information; things like screen colors, data drive assignments, and so on -- many of which are session-dependent. The users have a bad habit of using the "DOS Shell" command and then executing the application again, then EXITing back to the previous instance of the program. This doesn't cause a problem until some bright-minded individual exits the application, EXITs DOS, and winds up back in the first instance of the program. Any changes made in the second instance of the program have now been lost and the first instance may contain invalid configuration information.

To complicate matters, the application runs on a diskless network workstation, making it difficult to create a session configuration file. Add to this the TSR that will be communicating with and getting its configuration information from the application, and the need for system-wide global variables becomes evident.

My first reaction, of course, was to use the DOS environment variables; an approach I soon discarded when I found that child processes can't change the parent's environment. Next I tried back-chaining -- passing environment addresses from one program to the other, thereby always changing the ancestor's environment. This worked fine but for two drawbacks. First, if a change to an ancestor's environment would required more memory, DOS would allocate another block of memory above the currently active program -- thus forever trapping any programs between the ancestor and the newly allocated block. The other problem is with COMMAND.COM. Whenever a process shells to DOS, COMMAND.COM is loaded and assumes that it is Lord of the Universe inside your computer. Any back-chaining stops at the latest invocation of COMMAND.COM. I've heard that it's possible to back-chain beyond this point, but I'm not too sure the method is stable.

Several months ago, somebody put forth this problem on the CompuServe DDJ Forum. At that time, someone else suggested that a device driver be written that would support true global variables. I waited a while for the driver to appear but got impatient and decided to tackle it on my own.

The global variable device driver (GLOVAR.ASM, Listing One) is installed in your system at boot time. Simply place the line

  device=glovar.sys

in your CONFIG.SYS file and reboot the machine. Access to the global variables is provided through the use of the DOS read command and several functions that are provided in the device driver. Example programs in both Turbo C (GVAR.C, Listing Two and GVAR.H, Listing Three), and Turbo Pascal (GVAR.PAS, Listing Four) demonstrate the use of the driver. The Turbo Pascal program requires an assembly language interface to the driver (TPGVAR.ASM, Listing Five). Compile and link instructions are included as comments in the listings.

How to Use It

In order to access the global variables, an application program must first open the device (using the standard DOS open file function) and then read the address of the interface structure from the device. The read routine in the device driver is rather simple minded in that it assumes the application is requesting 4 bytes of information (the size of a far pointer). The device can be closed after this read, as all other functions are provided through direct calls to the driver's code. init and read are the only two DOS functions supported by the driver.

The interface structure contains four far pointers; a pointer to the memory block and a pointer to each of the three interface functions. This structure table is shown in the C header file (GVAR.H) and in the Turbo Pascal interface routines (TPGVAR.ASM).

The three functions provided by the device driver, their C and Pascal declarations, and a short description are shown shortly. The Pascal functions use a type VARSTR, that is defined as

  type varstr = string[255];
  function set_gvar (varname, vardef: varstr): boolean; external;
  int far pascal set_gvar (const char far * varname, const char far * vardef);

set_gvar assigns a value to a global variable. It returns TRUE (-1) if the variable was set successfully, or FALSE (0) if the global variable buffer in the driver is full. A variable can be removed by assigning it a null value (as in set_gvar ("VARNAME","");).

  Function get_gvar (varname: varstr; var vardef: varstr): boolean;
  external;
  int far pascal get_gvar (const char far *varname, char far * vardef);

get_gvar returns the value of a global variable previously defined using the set_gvar function. If the global variable exists, the function returns TRUE (-1) and the vardef parameter holds the value of the variable. If the global variable does not exist, the function returns FALSE (0), and the vardef parameter is a null string.

  procedure flush_gvars; external;
  void far pascal flush_gvars (void);

flush_gvars removes all variable definitions from the buffer.

Using the routines is really quite simple. In the C version, the three functions set_gvar(), get_gvar(), and flush_ gvars() are macros that expand to indirect function calls through pointers in the GVAR_DEF structure. The Pascal version calls assembly language subroutines that convert the passed arguments to ASCIIZ strings, and then perform an indirect function call in much the same manner as the C version.

There are some cautions and a few restrictions regarding use of the driver. First, because Turbo Pascal's strings have a maximum length of 255 bytes, there is a possibility of string overrun when using a Turbo Pascal program to retrieve variables set by C or assembly language programs. Neither the driver nor the Turbo Pascal interface routines address this issue. An overrun string will most likely cause your program to crash. The same goes for C programs -- if your buffer is smaller than the global variable, unpleasant things are bound to happen.

Global variable names may contain any characters other than the equal sign (=) and null (0). Variable values may contain any character other than null. All variable names are mapped to uppercase.

You may have noticed that the address of the memory block is provided in the GVAR_DEF structure. This pointer has been provided as a means to do other things with the block of reserved memory (such as passing data between programs). Note, however, that using the memory in this manner will interfere with any programs that are using the global variable routines.

How It Works

Upon initialization, the driver outputs a sign-on message, places the proper addresses in the interface structure (global_table), sets the required top-of-memory address for DOS, and flushes the global variable table. Because the initialization code is executed only once, the global variable table overlays it, thus reducing the amount of memory taken up by the driver.

The read call is equally simple. It merely takes the address of the interface structure and writes it as a long pointer into the buffer provided by DOS in the request header. It then sets the number of bytes read to 4 and exits. The set_gvar function works in two steps. First, it finds and removes the specified variable and its definition from the table, moving the rest of the entries to fill the space previously occupied by the now deleted variable. Then it installs the variable name (mapped to uppercase) with the new definition at the end of the table. This process is a bit slower than the "use-until-full-then-collect-garbage" method of managing string space, but it makes the driver code much simpler (and thus smaller). If I was dealing with a much larger block of memory (in the order of 10K or more), I'd worry more about the time involved in moving things around.

The get_gvar function uses the find_var internal function to locate the variable (if it exists), finds the beginning of the definition, and copies it into the buffer provided in the 'vardef' parameter.

The flush_gvars function is the ultimate in simplicity. It simply places two null bytes (ASCII 0) at the beginning of the table, thereby signifying the end of all defined strings.

I was a little apprehensive at first about attempting to write a DOS device driver. Happily, it turned out to be quite a bit easier than I had expected. All of the information required was gleaned from Ray Duncan's excellent book Advanced MS-DOS, Microsoft Press, 1986. In fact, the device driver template presented in that book formed the basis of my driver.

The most difficult part of this entire exercise was getting the Turbo Pascal interface functions to work. For some reason I couldn't get Turbo Debugger to work in source debugging mode with the assembly language functions. Most likely, I was doing something wrong. Testing the device driver was really quite simple. I originally wrote it to be linked into my Turbo C test program and used Turbo Debugger to test all the routines. When I was convinced it was stable, I made the necessary modifications, booted my system with the device driver installed, and was truly amazed when it worked the first time.

In its present state the driver will not support concurrent update by multiple processes. Adding this functionality would be a matter of returning a 'busy' status in response to a get, set, or flush call. This modification is left as an exercise to the reader.

While this driver does not provide permanent modification to an ancestor's environment, it does provide a safe, fast, documented way to maintain true system-wide global variables, thus achieving the desired effect without unnecessarily complicating the application program.

Availability

All source code for articles in this issue is available on a single disk. To order, send $14.95 (Calif. residents add sales tax) to Dr. Dobb's Journal, 501 Galveston Dr., Redwood City, CA 94063; or call 800-356-2002 (from inside Calif.) or 800-533-4372 (from outside Calif.). Please specify the issue number and format (MS-DOS, Macintosh, Kaypro).

_A Global Variable Device Driver for MS-DOS_ by Jim Mischel

[LISTING ONE]

<a name="0214_0008">

    name    globals
    page    ,132
    title   'GLOVAR - global variable device driver
;
; GLOVAR.ASM
;
; Copyright 1989, Jim Mischel
;
; This MS-DOS device driver provides true global variables to application
; programs.
;
; Sample make file:
; #
; # glovar.mak - build global variable device driver glovar.sys.
; #
; glovar.sys: glovar.asm
;   tasm glovar
;   link glovar;
;   exe2bin glovar.exe glovar.sys
;
    ideal
    locals

    model   tiny,pascal
    CodeSeg

    org 0       ;device drivers start at offset 0

max_cmd     equ 12  ;only 12 functions recognized by the driver
cr      equ 0dh
lf      equ 0ah
buff_size   equ 1024    ;buffer size in bytes
TRUE        equ -1
FALSE       equ 0

;
; Device Driver Header
;
header  dd  -1      ;link to next device, -1 = end of list
        dw  8000h       ;simple character device driver
    dw  strat       ;device Strategy entry point
    dw  intr        ;device Interrupt entry point
    db  '$$GVAR$$'  ;Device name

rh_ptr  dd  ?       ;saved request header pointer

;
; Command codes dispatch table.  Only functions 0 (initialize) and 4 (read)
; are supported.  Other functions go to a stub routine that simple returns
; a non-error status.
;
dispatch:
    dw  init        ; 0 = initialize driver
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  read        ; 4 = read from device
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
    dw  not_imp     ; not implemented
;
; MS-DOS Request Header structure definition
;
struc   request
rlength db  ?       ;length of request header
unit    db  ?       ;unit number for this request (block devices)
command db  ?       ;request header's command code
status  dw  ?       ;driver's return status word
reserve db  8 dup (?)   ;reserved area
media   db  ?       ;media descriptor byte (block devices)
address dd  ?       ;memory address for transfer
count   dw  ?       ;byte/sector count value
sector  dw  ?       ;starting sector value (block devices)
ends    request         ;end of request header template

; Status codes returned by routines in AX
error       equ 8000h
busy        equ 0200h
done        equ 0100h
unknown_command equ 3

;
; Device strategy routine -- save address of request header.
;
strat:  mov [word ptr cs:rh_ptr],bx
    mov [word ptr cs:rh_ptr+2],es
    retf

;

; Interrupt routine.  Dispatch to the proper routine.
;
proc    intr    far uses ax bx cx dx ds es di si
    pushf
    push    cs
    pop ds              ;point to local data
    les di,[rh_ptr]         ;ES:DI = Request Header
    mov bl,[es:di+request.command]  ;bx = command code
    xor bh,bh
    cmp bx,max_cmd          ;make sure it's legal
    jle intr1
    mov ax,error+unknown_command    ;Error:  unknown command
    jmp short intr2
intr1:  shl bx,1                ;form index to dispatch table
    call    [word ptr bx+dispatch]      ;and branch to driver routine
    les di,[rh_ptr]         ;ES:DI = request header
intr2:  or  ax,done             ;set Done bit
    mov [es:di+request.status],ax   ;store status in request header
    popf
    ret
endp    intr

;
; 'stub' routine for un-implemented functions.
;
not_imp:
    xor ax,ax           ;set good status
    retn

;
; Read -- return address of 'global_table'
;
; This routine assumes that the application program asked for 4 (no less!)
; bytes (the size of a far pointer).
;
; This function is called with ES:DI pointing to the MS-DOS request header.
;
read:   lds si,[dword ptr es:di+request.address]    ;DS:SI is buffer address
    mov [word ptr ds:si],offset global_table    ;store offset
    mov [word ptr ds:si+2],cs           ;and segment
    mov [word ptr es:di+request.count],4    ;4 bytes transferred
    xor ax,ax                   ;return good status
    ret

;
; The following three routines (set_var, get_var, flush_vars) are accessed
; by applications programs through FAR calls.  The addresses of these
; functions are provided in the 'global_table' structure, the address of
; which is passed to the application when it does a read on the $$GVAR$$
; device.
;

;
; set_var -- assign a value to the global variable pointed to by the
; 'varname' parameter.  If the 'vardef' parameter points to the null string,
; the variable is removed from the table.
;
; Returns TRUE (-1) if successful, FALSE (0) if variable table overflows.
;
proc    set_var far uses si di ds, varname:far ptr, vardef:far ptr
    lds si,[varname]
    call    near ptr remove_var     ;try to remove variable
    les di,[vardef]
    cmp [byte ptr es:di],0      ;check for NULL string
    jnz do_set
    mov ax,-1               ;and if so, just call it quits
    jmp short set_done
do_set: lds si,[varname]            ;otherwise,
    call    near ptr install_var        ;install the new def
set_done:
    ret
endp    set_var

;
; Return the definition of the variable pointed to by 'varname' in the
; space pointed to by 'vardef'.  Returns -1 if the variable exists, 0 if
; not.  If the variable does not exist the null string is returned in 'vardef'.
;
proc    get_var far uses si di ds, varname:far ptr, vardef:far ptr
    lds si,[varname]
    call    find_var
    jnc get1            ;if found, continue
    les di,[vardef]     ;otherwise,
    mov [byte ptr es:di],0  ;store NULL string
    xor ax,ax           ;set failure status
    jmp short @@done        ;and exit
get1:   cld             ;variable found, now find '='
    mov al,'='
    mov cx,-1
    repnz   scasb
    mov si,di
    push    es
    pop ds          ;DS:SI now points to first character of definition
    les di,[vardef]     ;ES:DI points to return vardef area
@@loop: lodsb               ;copy the definition to
    stosb               ;the 'vardef' string
    or  al,al
    jnz @@loop
    mov ax,TRUE         ;set status to TRUE
@@done: ret
endp    get_var

;
; flush all variables from the global variable table.  This is done by placing
; a NULL byte at the start of the table and updating the 'next_mem' variable
; so that it also points to the head of the table.
;
flush_vars:
    mov [word ptr cs:glovars],0
    mov ax,offset glovars
    mov [word ptr cs:next_mem],ax
    retf

;
; Support routines.
;
; Remove the variable (name pointed to by ds:si) and its definition from
; the global variables table.
;
remove_var:
    call    find_var            ;find the variable
    jc  @@done
    cld
    mov si,di               ;SI is start of variable name
    mov al,0
    mov cx,-1
    repnz   scasb               ;move past definition
    add [word ptr cs:next_mem],cx   ;update next_mem pointer
    inc [word ptr cs:next_mem]
    xchg    si,di               ;si=next variable, di=old var
    push    cs
    pop ds
    mov cx,offset end_buff
    sub cx,si               ;cx is byte count
    rep movsb               ;variable has been erased
@@done: ret

;
; Called with ds:si pointing to variable name, es:di pointing to definition.
;
install_var:
    push    es
    push    di          ;save 'vardef' address
    cld
    mov al,0
    mov cx,-1
    repnz   scasb           ;get length of definition
    not cx
    mov bx,cx           ;and save
    push    bx          ;will be used to copy the definition
    push    ds
    pop es
    mov di,si
    mov cx,-1
    repnz   scasb           ;get length of variable name
    not cx
    push    cx          ;will be used to copy the variable name
    add bx,cx           ;bx=strlen(varname)+strlen(vardef)+2
    mov ax,buff_size + offset glovars
    sub ax,[word ptr cs:next_mem]
    cmp ax,bx
    jnc inst1           ;if not enough room
    xor ax,ax           ;then fail
    add sp,8            ;clean up stack
    ret
inst1:  push    cs
    pop es
    mov di,[word ptr cs:next_mem]   ;ES:DI points to buffer
    pop cx          ;restore varname length
    dec cx          ;don't want null terminator
@@loop1:
    lodsb
    call    toupper         ;convert char to upper_case
    stosb               ;and store
    loop    @@loop1
inst2:  mov al,'='
    stosb               ;store the '=' separator
    pop cx          ;restore definition length
    pop si          ;restore 'vardef' address
    pop ds
    rep movsb           ;and copy definition to buffer
    mov [word ptr cs:next_mem],di
    stosb               ;store extra null byte for safe keeping
    mov ax,TRUE
    ret

;
; find_var -- attempt to find the variable pointed to by DS:SI in the
; global variables buffer.
; On success, AX = TRUE (-1), carry is cleared, and ES:DI points to the first
; character of the variable name in the buffer.
; If the variable is not found, AX = FALSE (0), carry flag is set, and ES:DI
; is undefined.
;
find_var:
    cld
    push    cs
    pop es
    mov di,offset glovars
    mov dx,si           ;save name starting address
find0:  mov bx,di           ;and variable starting address
    mov si,dx
    dec di
find1:  lodsb
    call    toupper
    inc di
    cmp al,[byte ptr es:di]
    jz  find1
    or  al,al           ;didn't match, at end of string?
    jnz get_next_var        ;if not, go for next variable
    cmp [byte ptr es:di],'='    ;at end of varname in buffer?
    jnz get_next_var        ;if not, do next variable
    mov di,bx           ;variable found
    mov ax,TRUE
    ret
get_next_var:
    mov al,0
    mov cx,-1
    repnz   scasb           ;get start of next variable
    cmp [byte ptr es:di],0  ;if not at end of buffer
    jnz find0           ;then do next variable
    xor ax,ax
    stc             ;the variable wasn't found
    ret
;
; toupper - convert the character in AL to upper-case.
;
toupper:
    cmp al,'a'
    jc  tod
    cmp al,'z'+1
    jnc tod
    sub al,32
tod:    ret

    even                ;might as well

;
; The address of this table is passed back to the application whenever
; a 'read' call is made on the device.
; The application uses this table to access the memory buffer and the
; driver functions.
;
global_table:
    dd  ?           ;address of memory buffer
    dd  ?           ;address of set_var routine
    dd  ?           ;address of get_var routine
    dd  ?           ;address of flush_vars routine

next_mem dw offset glovars      ;pointer to end of defined variables

glovars:                ;start of glovar buffer

;
; Initialization code.
;
; Initialize the function pointers table and the global data buffer, and
; return the address of the first byte of free memory after all the device
; driver code.  The global variables buffer overlays this initialization code.
;
; Upon entry, ES:DI points to the MS-DOS request header.  DS contains the
; local data segment (same as CS).  This routine makes no attempt to save
; any registers.
;
init:   mov ax,cs
    mov es,ax
    mov di,offset dhaddr
    call    hexasc          ;convert load segment address to ASCII
    mov ax,cs
    mov es,ax
    mov di,offset mbaddr
    call    hexasc
    push    cs
    pop es
    mov ax,offset glovars
    mov di,offset mbaddr+5
    call    hexasc
    mov ah,9            ;print sign-on message and
    mov dx,offset ident     ;driver load address
    int 21h
    les di,[rh_ptr]     ;request header address in ES:DI
;store first usable memory address in request header
    mov [word ptr es:di+request.address],offset end_buff
    mov [word ptr es:di+2+request.address],cs
;
; Now set up the 'global_table' structure with proper memory addresses.
;
    mov [word ptr global_table],offset glovars
    mov [word ptr global_table+2],cs
    mov [word ptr global_table+4],offset set_var
    mov [word ptr global_table+6],cs
    mov [word ptr global_table+8],offset get_var
    mov [word ptr global_table+10],cs
    mov [word ptr global_table+12],offset flush_vars
    mov [word ptr global_table+14],cs
    xor ax,ax               ;return good status
    mov [word ptr glovars],ax       ;and clear glovars buffer
    ret

ident   db  cr,lf,lf
    db  'Global variable device driver version 1.0',cr,lf
    db  'Copyright 1989, Jim Mischel',cr,lf
    db  'Device Header at '
dhaddr  db  'XXXX:0000.  '
    db  'Memory buffer at '
mbaddr  db  'SSSS:OOOO',cr,lf,lf,'$'

;
; hexasc - converts a binary 16-bit number into a hex ASCII string
;
; call with AX = value to convert, ES:DI = address to store 4-character
; string.
;
hexasc: mov bx,ax       ;save value here
    mov cx,4        ;initialize character counter
    mov dx,cx
hexasc1:
    xchg    cx,dx
    rol bx,cl       ;isolate next 4 bits
    mov al,bl
    and al,0fh
    add al,'0'      ;convert to ASCII
    cmp al,'9'      ;if 0-9
    jbe hexasc2     ;then jump
    add al,'A'-'9'-1    ;otherwise add offset for A-F
hexasc2:
    stosb
    xchg    cx,dx
    loop    hexasc1
    ret

; reserve space for rest of global variable buffer.
    db  buff_size - (offset $ - offset glovars) + 1 dup (0)

    even
end_buff:
    end





<a name="0214_0009"><a name="0214_0009">
<a name="0214_000a">
[LISTING TWO]
<a name="0214_000a">


/*
 * GVAR.C - program to test global device driver interface.
 * Required structure and function definitions are in the file GVAR.H
 *
 * Sample make file.
 *
 * #
 * # tcgvar.mak - make C gvar test program
 * #
 * gvar.exe: gvar.c gvar.h
 *   tcc -ms gvar
 *
 */
#include <stdio.h>
#include <conio.h>
#include <fcntl.h>
#include <io.h>
#include "gvar.h"

void main (void) {
    char buff[1024];
    int fhandle;

    if ((fhandle = _open ("$$GVAR$$", O_RDONLY)) == -1) {
    puts ("Error:  can't open global variables file");
    return;
    }
    read (fhandle, &gvars, sizeof (char far *));
    printf ("global table at %Fp\n", gvars);
    _close (fhandle);
    if (!set_gvar ("JIMSVAR", "Hi jim.!"))
        puts ("Error setting variable JIMSVAR");
    if (!get_gvar ("jimsvar", buff))
        puts ("JIMSVAR not defined");
    else
        printf ("JIMSVAR=%s\n", buff);
    set_gvar ("jimsvar","");
    if (!get_gvar ("JIMSVAR", buff))
        puts ("JIMSVAR not defined");
    else
        printf ("JIMSVAR=%s\n", buff);
}






<a name="0214_000b"><a name="0214_000b">
<a name="0214_000c">
[LISTING THREE]
<a name="0214_000c">

/*
 * gvar.h -- required definitions for C interface to global variable
 * device driver.
 *
 * Copyright 1989, Jim Mischel
 */

/*
 * structure of table returned by glovar read call.
 */
typedef struct {
    void far *mem_buff;     /* pointer to memory buffer */
/* function pointers */
    int  far pascal (far *set_var)    (char far *, char far *);
    int  far pascal (far *get_var)    (char far *, char far *);
    void far pascal (far *flush_vars) ();
} GVAR_DEF;

GVAR_DEF far *gvars;    /* global pointer to global variables structure */

/*
 * function macros
 */
#define set_gvar (*gvars->set_var)
#define get_gvar (*gvars->get_var)
#define flush_gvars (*gvars->flush_vars)






<a name="0214_000d"><a name="0214_000d">
<a name="0214_000e">
[LISTING FOUR]
<a name="0214_000e">

{*
 *  GVAR.PAS - Test global variable device driver.
 *
 * This program depends on the routines in TPGVAR.ASM to provide the interface
 * to the device driver.
 *
 * Make sure the file tpgvar.obj is in the current directory
 * (or Turbo's /Object directory) before compiling this program.
 *
 * Sample make file for compiling this program:
 * #
 * # tpgvar.mak - make pascal gvar test program
 * #
 * gvar.exe: gvar.pas tpgvar.obj
 *   tpc gvar
 *
 * tpgvar.obj: tpgvar.asm
 *   tasm tpgvar
 *
 *}

{$F+}           { asm routines require far calls }
{$L tpgvar}
program test_globals;
type
    varstr  = string[255];

var
    buff : varstr;

function gvar_init : boolean; external;
function set_gvar (varname : varstr; vardef : varstr) : boolean; external;
function get_gvar (varname : varstr; var vardef : varstr) : boolean; external;
procedure flush_gvars; external;

begin
    if (not gvar_init) then begin
    writeln ('Can''t open gvars file.  Program aborted.');
    halt;
    end;
    if (not set_gvar ('JIMSVAR', 'HELLO THERE'))
        writeln ('Error setting variable JIMSVAR');
    if (not get_gvar ('jimsvar', buff))
        writeln ('JIMSVAR not defined')
    else
        writeln ('JIMSVAR=', buff);
    trash := set_gvar ('jimsvar','');
    if (not get_gvar ('jimsvar', buff))
        writen ('JIMSVAR not defined')
    else
        writeln ('JIMSVAR=', buff);
end.





<a name="0214_000f"><a name="0214_000f">
<a name="0214_0010">
[LISTING FIVE]
<a name="0214_0010">


    page    ,132
    name    tpgvar
    title   'TPGVAR -- Turbo Pascal global variable interface routines'
;
; TPGVAR.ASM
; These routines provide the Turbo Pascal interface to the global variable
; device driver.
;
; The routines provided are:
;
; GVAR_INIT
; function gvar_init : boolean; external;
;   Returns TRUE if successful, FALSE otherwise.  MUST be called before using
;   any of the other functions.
;
; SET_GVAR
; function set_gvar (varname : varstr; vardef : varstr) : boolean; external;
;   Defines the variable 'varname' with the value 'varstr'.  Returns FALSE
;   if inserting this variable will over-run the global variables buffer.
;
; GET_GVAR
; function get_gvar (varname : varstr; var vardef : varstr) : boolean; external;
;   Return the definition of the variable 'varname' in the string 'vardef'.
;   Returns FALSE if 'varname' doesn't exist.
;
; FLUSH_GVARS   procedure flush_gvars; external;
;   Removes all global variables from the buffer.
;
; Copyright 1989, Jim Mischel
;
    model   tpascal
    ideal
    locals

;
; global variable interface structure
; A pointer to this type of structure is returned by the $$GVAR$$ device
; in response to a read call.
;
struc gvars
membuff_ptr dd  ?
set_var_ptr dd  ?
get_var_ptr dd  ?
flush_vars_ptr  dd  ?
ends

    CodeSeg
;
; This has to go into the code segment because 'TPascal' model won't allow
; initialization in the data segment.
;
gvar_filename   db  '$$GVAR$$',0    ;global variable device name
gvar_ptr    dd  ?       ;pointer to global variables structure
glovar_name db  256 dup (?) ;passed to driver routines
glovar_def  db  256 dup (?)

public  GVAR_INIT
public  SET_GVAR
public  GET_GVAR
public  FLUSH_GVARS

;
; open the $$GVAR$$ file and read the address of the header into gvar_ptr
; returns TRUE if successful, FALSE if not.
;
proc    gvar_init uses ds
    push    cs
    pop ds
    mov dx,offset gvar_filename
    mov ax,3d00h
    int 21h         ;open the file
    jc  @@error         ;error opening file
    push    ax          ;save file handle
    mov bx,ax           ;file handle in BX
    mov dx,offset gvar_ptr
    mov ah,3fh
    mov cx,4
    int 21h         ;read address of gvar structure
    pop bx          ;file handle in BX
    mov ah,3eh
    int 21h         ;close the file
    mov ax,-1
    jmp short @@done
@@error:
    xor ax,ax
@@done: ret
endp    gvar_init

;
; This macro converts the Turbo Pascal string in the 'from' parameter to an
; ASCIIZ string in the 'to' parameter.
;
macro @make_asciiz from, to
    lds si,[&from&]
    push    cs
    pop es
    mov di,offset &to&
    push    cs          ;these values are pushed now, but not
    push    di          ;used until the gvar call below
    call    near ptr make_asciiz
endm

;
; Macro calls the specified routine.
;
macro @gvar_call routine
    les bx,[cs:gvar_ptr]
    call    [dword ptr es:bx+gvars.&routine&]
endm

;
; Define 'varname' with value 'vardef'.  Returns TRUE if successful, FALSE
; if the global variables buffer overflows.
;
proc    set_gvar uses ds, varname:far ptr, vardef:far ptr
    @make_asciiz varname, glovar_name
    @make_asciiz vardef, glovar_def
    @gvar_call set_var_ptr
    ret
endp    set_gvar

;
; Return definition of 'varname' in 'vardef'.  Returns TRUE if successful,
; FALSE if 'varname' does not exist.  If 'varname' does not exist, 'vardef'
; is set to the null string.
;
; CAUTION:  if the returned variable definition is longer than 255 characters,
; many strange and not-so-wonderful things will happen.
;
proc    get_gvar uses ds, varname:far ptr, vardef:far ptr
    @make_asciiz varname, glovar_name
    push    cs
    mov ax,offset glovar_def
    push    ax
    @gvar_call get_var_ptr      ;get the variable
    push    ax          ;save return status
    push    cs          ;now convert definition to TP string
    pop ds
    mov si,offset glovar_def
    les di,[vardef]
    push    di          ;save to set length
    inc di          ;start string at second byte
    xor cx,cx           ;cx is byte count
    cld             ;better safe than sorry
@@loop: lodsb
    or  al,al
    jz  @@done
    stosb
    inc cx          ;bump length
    jmp short @@loop
@@done: pop di          ;restore pointer to length byte
    mov [byte ptr es:di],cl ;and store length there
    pop ax          ;restore return status
    ret
endp    get_gvar

;
; flush all global variables
proc    flush_gvars
    @gvar_call flush_vars_ptr
    ret
endp    flush_gvars

;
; convert Turbo Pascal string into ASCIIZ string.
; Call with ds:si pointing to source string (TP format)
;           es:di pointing to destination buffer
; Make sure this is accessed through a near call.
;
make_asciiz:
    lodsb               ;get length
    mov cl,al
    xor ch,ch           ;in CX
    cld
    rep movsb           ;move the string
    mov [byte ptr es:di],0  ;and place the null terminator
    retn
    end













Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.