home *** CD-ROM | disk | FTP | other *** search
-
- ; Quick Basic Redirected File Display
- ;
- ;
- ; ---- A modified version of the original PD program REDVIEW.ASM ----
- ; by Alexandr Novy and Peter Horak of Prague, Czechoslovakia
- ;
- ;
- ; ---- This Public Domain Adaptation for QuickBasic Written By:
- ;
- ; Peter R. Barnes
- ; Brentwood, TN
- ; March, 1992
- ;
- ;
- ; ---- To assemble the object module with TASM, use the command:
- ;
- ; TASM QBREDIR ;I don't have MASM,
- ; ;but I'm sure the
- ; ;syntax is the same
- ;
- ;
- ; ---- To use in a QuickBasic program, compile your program
- ; and link the module to your code, for example:
- ;
- ; BC /options YOURPROG; ;any options you want
- ;
- ; LINK /linkoptions YOURPROG + QBREDIR;
- ;
- ;
- ;
- ; ---- To call the routines in your program, use the following:
- ;
- ; CALL QFRedSet (FlagIntegerVariable%)
- ;
- ; CALL QFRedOff
- ;
- ;
- ; --- IMPORTANT: READ THE QBREDIR.DOC FILE TO SEE HOW TO
- ; PROPERLY USE THESE ROUTINES!! THE ROUTINES
- ; INSTALL A CUSTOM INTERRUPT HANDLER WHICH
- ; MUST BE TREATED WITH CARE!!
- ;
- ; ---- NOTE: CANNOT BE USED IN THE QB ENVIRONMENT (SEE
- ; QBREDIR.DOC)
- ;
- ;
- ; ---- I have added some comments to help explain the code as
- ; it applies to QuickBasic, just for general (and my own)
- ; reference. Some of the stuff is pretty elementary, so if
- ; you are an assembly language wiz, just skip it; it's there
- ; for the rest of us.
- ;
- ;
- ;********************************************************************
- ;********************************************************************
- ;
- ; Make our procedure names available to external programs
- ;
- public QFRedSet, QFRedOff
- ;
- ;
- ;********************************************************************
- ;
- ; All Basic modules are assembled using the Medium
- ; memory model, so we must add the proper assembler directives
- ;
- ;
- .MODEL Medium, Basic ;always, for QuickBasic
- ;
- .CODE ;make the code segment our default
- ;
- ;
- ;
- ; We will let QBasic and TASM set the type of procedure, and we will
- ; declare the module using the TASM simplified format
- ;
- ;
- QFRedSet proc uses si di es ds
- ARG flag:PTR = ARGLEN
- ;
- ;
- ; That tells TASM to add code for a Basic procedure call, save
- ; si,di,es,ds pointers, and that we are passing to the procedure a
- ; two-byte pointer to the INTEGER (notice that I capitalized that --
- ; three guesses why) variable /flag/, consisting of ARGLEN number of
- ; bytes -- the QuickBasic 'call' automatically pushes that address onto
- ; the stack and TASM calculates the correct offset to those stack
- ; bytes when it assembles the code. All parameter addresses passed by
- ; QuickBasic are near references (offset only, 1 word or 2 bytes)
- ; unless you use the keyword SEG for the passed variable, which tells
- ; QuickBasic to pass the address as a far reference (segment:offset,
- ; 2 words or 4 bytes).
- ; ---------
- ; TASM knows from our 'Basic' directive that the variable we pass will
- ; be a 2-byte address, and that Basic always makes far calls, leaving a full,
- ; 2-word return address as the last 4 bytes on the stack; the variable
- ; address, next on the stack, will be at [B(ase)P(ointer)+6] from the
- ; present BP value (+ because the stack grows downward in memory).
- ; That's also how TASM knows that we need a far return, not a near
- ; return, and also how many bytes to discard, after it pops that QB
- ; return address, to restore the stack after the procedure ends --
- ; i.e. 2 bytes for the variable near address...
- ;
- ; push bp ;TASM automatically
- ; mov bp,sp ;adds the code to save the B(ase)
- ; ;P(ointer) to the stack in the stack
- ; ;segment, and set up a new stack
- ; ;that starts at the end of the old
- ; ;one pointed to by the current
- ; ;S(tack)P(ointer) -- i.e.
- ; New bp = Old bp + sp
- ; ;Actually, our new stack will begin
- ; ;at New bp + sp, but this will keep
- ; ;us away from the old stack for
- ; ;all values of sp, in case sp gets
- ; ;set to a lower value -- we will
- ; ;never get lower than the new bp.
- ; ;Of course, we have to be careful
- ; ;to preserve that sp value in our
- ; ;code by always 'popping' the same
- ; ;number of bytes that we 'push'
- ; ;onto the stack.
- ;
- ;
- ; ;This is the code that is added
- ; ;by TASM when it sees the 'Basic'
- ; push si ;and the 'uses' directives, so we
- ; push di ;don't need the explicit statements.
- ; push es ;The address of /flag/ is placed on
- ; push ds ;the stack first, and ARGLEN tells
- ; ;TASM how many bytes to discard, when
- ; ;we return from the call, to get
- ; ;back to the original stack before
- ; ;the call.
- ;
- ;
- ;********************************************************************
- ;
- ;
- ;
- ; The first thing we do is grab and save the address of the QB variable
- ; that was passed to us, on the stack, by QuickBasic when it made the
- ; call; remember that TASM knows that [flag] means [BP+6]. In this
- ; call, we passed the 2-byte offset address of the variable in the
- ; ds data segment, so the 'word ptr [flag]' will have the 2-byte word
- ; for the offset moved into ax; then the 2-byte word for the ds data
- ; segment, whatever it is, will be stored. Both of those values are
- ; copied into /flagadr/ by the following 3 mov instructions:
- ;
- ;
- mov ax,word ptr [flag] ;save the offset of passed
- mov cs:flagadr,ax ;integer variable /flag/ in
- ; ;memory location /flagadr/
- ;
- ;
- ; That saves the offset that we passed, now we save the data segment
- ; pointer that is in ds at the time of the call. Since QuickBasic
- ; only has one data segment for integer variables, ds will already be
- ; pointing to the segment containing our /flag/ variable.
- ;
- ; We could pass the segment pointer when we made the call from
- ; QuickBasic by preceding the variable name with the SEG keyword; in
- ; that situation, QuickBasic would push the full 4-byte address onto
- ; the stack. We could also use the CALLS statement to call this
- ; routine, instead of CALL, because that statement always passes the
- ; full address of parameters. But neither of those actions are
- ; required, because QuickBasic only has one data segment
- ;
- ; The following mov instruction saves our data segment for DOS to
- ; use to find our variable when it executes the interrupt routine:
- ;
- ;
- mov cs:flagadr+2,ds ;save the segment pointer
- ; ;of passed integer variable
- ; ;/flag/ in second word
- ; ;of /flagadr/ --
- ; ;that's what DOS needs when
- ; ;the interrupt is called
- ;
- ;
- ; Next step is flagcheck to prevent multiple calls to this routine
- ; if it has not been reset. Without this check, a second call would
- ; save our new handler address as the original.
- ;
- ; Check to see if we have already installed the vector to the
- ; interrupt handler routine. If we have, we don't want to do it
- ; again, because we will lose the original vector
- ;
- mov al,cs:[IntSetFlag] ;get flag to see if handler
- cmp al,0 ;is already installed
- jne EndSet ;if set, we have installed
- ; ;so go to exit
- ;
- mov al, 1 ;else set the flag
- mov cs:[IntSetFlag],al
- ;
- ;
- ; ;pick up original vector contents
- ;
- mov ax,3521H ;for interrupt 21H (MS-DOS Services
- int 21H ;Function Request handler)
- ;
- mov cs:Old21Offset,bx ;save the original vector
- mov cs:Old21Segment,es ;in our data area
- ;
- ;
- ; ;a call to DOS services INT21H for
- ; ;the SET INTERRUPT function 25xxH
- ; ;requires ds:dx to be set to the
- ; ;interrupt address, so the next two
- ; ;statements set the ds segment
- ; ;equal to the cs segment, where
- ; ;our new handler procedure is
- ; ;located
- ;
- ;
- push cs ;push our code segment onto stack
- pop ds ;pop it back off to set ds
- ; ;i.e. sets ds=cs
- ;
- mov dx,offset QDualDisp ;load offset of our Int 21 handler
- mov ax,02521H ;and reset vector to point to
- int 21H ;our procedure
- ;
- ;
- ; pop ds ;restore registers and
- ; pop es ;return to our QB program
- ; pop di
- ; pop si ;NOTE--TASM automatically adds
- ; pop bp ;the code to restore registers
- ;
- EndSet: ret
- ;
- ; actually, it's 'ret [ARGLEN]' ;TASM automatically makes the
- ; ;return discard the /flag/ address
- ; ;bytes from the stack--the number
- ; ;of bytes to discard was set by
- ; ;the ARGLEN directive
- ;
-
- ;
- ;
- ; Following is our new interrupt handler which is called by
- ; MS-DOS whenever an Interrupt 21 MS-DOS Services Request
- ; is detected. The procedure first checks our enable flag
- ; to determine if we want the dual output activated; the address of
- ; this flag was passed by the QB program during the call to
- ; the QFRedSet function. If the flag is not set, we just skip our
- ; handler and proceed to the regular interrupt 21 routine.
- ;
- ; The handler then checks to see what type of service is being requested.
- ; If it is a call to the DOS routines to output a character or a string
- ; to STDOUT, the standard DOS output device, which is redirectible and
- ; is always designated with file handle 1, then our procedure essentially
- ; duplicates the routine by sending the output to STDERR, the standard
- ; DOS error output device, which is always the display, file handle 2,
- ; and whose output cannot be redirected. Then the handler exits to the
- ; normal Interrupt 21 procedure to let it process the original call.
- ;
- ; Note that the routine is part of the QFRedSet procedure, but it is never
- ; executed when that procedure is called by the QB program, because that
- ; procedure ends at the 'ret' opcode at EndSet, above.
- ;
- QDualDisp:
-
- pushf ;save everything we can --
- push ax ;i.e. all registers and flags
- push bx
- push cx ;note that we save the flags
- push dx ;even though DOS did the same
- push bp ;thing when it called the
- push si ;interrupt routine
- push di
-
-
- push bx ;save entering register values
- push ds
- mov bx,cs:flagadr ;get address of flag value passed
- mov ds,cs:flagadr+2 ;from our QB program
- mov al,ds:bx ;get flag value into al
- pop ds ;retrieve register values
- pop bx
-
- or al, al ;see if value was zero
- je EndOur21 ;yes, don't do anything, go
- ;to the regular interrupt 21 routine
-
- cmp ah,02 ;was it a call to write character to STDOUT
- jne Int9Chk ;no, jump to next check
-
- push ds ;yes, save entering data segment value
- push cs ;set ds=cs
- pop ds
- mov cs:character,dl ;save the character to write in character
- mov ah,40h ;set up for int 21 function 40h
- ;to write character to output device
- mov bx,2 ;set file handle=2 for STDERR
- mov dx,OFFSET character ;get the pointer to the character
- mov cx,1 ;one character to be written
- pushf ;save current flag status
- call dword ptr cs:[Old21];use old interrupt routine to output
- ;the character to the display,
- ;it will pop the flags saved on stack
- ;when it returns, because it ends with
- ;iret, since it is an interrupt routine
- pop ds ;retrieve our character
- jmp short EndOur21 ;exit to old routine to write the character
- ;to redirected output
-
- Int9Chk:
- cmp ah,09 ;was it a call to write string to output
- jne Int40Chk ;no, go to next check
-
- Chrloop:
- mov si,dx ;yes, set index
- cmp byte ptr ds:[si],'$';are we at the end of the string
- je End2109 ;yes, go to finish
- mov ah,40h ;no, set up for int 21 function 40h
- mov bx,2 ;output to STDERR
- mov cx,1
- push dx
- pushf
- call dword ptr cs:[Old21];write the character as above
- pop dx
- inc dx ;bump index to next character
- jmp short Chrloop ;loop until done
- End2109:
- jmp short EndOur21 ;exit to old routine to write the character
- ;to redirected output
-
- Int40Chk:
- cmp ah,40h ;was it a call to write to file or device
- jne EndOur21 ;no, done, exit to regular int 21
-
- cmp bx,1 ;yes, then was it a call to STDOUT
- jne EndOur21 ;no, done, exit
-
- push ds ;yes, save character
- mov bx,2 ;set file handle for STDERR
- pushf
- call dword ptr cs:[Old21] ;use old interrupt routine to output
- pop ds ;retrieve character
-
- EndOur21:
-
- ;retrieve registers
- pop di ;this resets registers
- pop si ;and flags to exactly
- pop bp ;where they were when we
- pop dx ;entered our custom handler
- pop cx
- pop bx
- pop ax
- popf
- ;
- ;
- jmp dword ptr cs:[Old21] ;go to regular int 21 routine
- ;
- ;
- ;
- ;
- QFRedSet endp
- ;
- ;
- ;
- ; The subroutine QFRedOff is called by the QB program to
- ; return the MS-DOS interrupt vector 21H to its' original state.
- ; The subroutine is used in the form:
- ;
- ; CALL QFRedOff()
- ;
- ;
- QFRedOff proc uses si di es ds ;Restore MS-DOS Services
- ; ;interrupt vector 21H
- ; push bp ;to its' original state
- ; mov bp,sp
- ; push ds ;same code added as above
- ; etc.
- ;
- ;
- ; First we must check our IntSetFlag to see if the
- ; vectors to our handler were installed; if not, we
- ; do not want to reset them here, either
- ;
- ;
- mov al,cs:[IntSetFlag] ;Get our inhibit flag
- cmp al,1 ;see if it is set
- jne EndOff ;no, go to exit
- ;else, reset vector
- ;
- ;
- mov dx,cs:Old21Offset ;Set interrupt 21H MS-DOS
- mov ds,cs:Old21Segment ;Services Request
- mov ax,02521H ;back to its' original vector
- int 21H
- ;
- ;
- mov al, 0 ;reset the flag to enable
- mov cs:[IntSetFlag],al ;the QFRedSet routine
- ;
- ; pop ds ;restore registers and
- ; pop bp ;return to QB program --
- ; etc. ;same exit code added as above
- ;
- EndOff: ret
- ;
- QFRedOff endp
- ;
- ;
- ;
- IntSetFlag db 0 ;this flag inhibits QFRedSet operation
- ; ;after it has redirected vector
- ; ;until QFRedOff resets it, and does
- ; ;the same for QFRedOff if QFRedSet
- ; ;has not redirected vector
- ;
- flagadr dw 0,0 ;Long address of QB program's
- ; ;display redirected output flag --
- ; ;note that each '0' is actually 2 bytes
- ;
- Old21 Label Dword
- Old21Offset dw ? ;Original contents of MS-DOS
- Old21Segment dw ? ;Interrupt 21H vector
-
- ;
- ;
- character DB ? ;storage for output character
- ;
-
- end
-
-