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]
Copyright © 1989, Dr. Dobb's Journal<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