The QBNews Page 33 Volume 1, Number 2 February 2, 1990 ---------------------------------------------------------------------- S o m e A s s e m b l y R e q u i r e d ---------------------------------------------------------------------- Using the DOS EXEC function from QB by Harold Thomson ** Caution - the following program will not work in the ENVIRONMENT ** I have been monitoring the messages in the QB message bases for the last few months and one of the things that I noticed is that a lot of people wish the the BASIC SHELL command would return an ERRORLEVEL if the child program returns one. I don't think that this is asking too much and I cannot figure out why MS didn't impliment it. Seeing this gave me one more routine to add to my growing library of routines for QB4 and above that I have written entirly in MS assembler. Right now the library is at version 4.5 and contains over 130 routine, but enough for the sales pitch (BTW, it's free). Before we get into the program, a little about the DOS EXEC function 4BH along with the related function, 4DH Get return code which is used by a parent process, after the successful execution of an EXEC call to obtain the return code and termination type of a child process. When I first saw the EXEC function, I realized that this was something that I could make use of. But as I started reading the documentation, I really started to get confused. It talked about different blocks that were needed, the parameter block, the environment block, the command tail as well as the ASCIIZ program pathname. I was almost tempted to say that it was time to start another project. Instead I just picked up my worn copy of ADVANCED MSDOS PROGRAMMING by Ray Duncan and worked my way thru his explanation and examples and was able to make some sense of it. The results of it follows. As I said above, there are some blocks that are necessary in order to get the EXEC function to work. These are commented in the followingexample but here is a brief description: PARAMETER BLOCK Contains the addresses of four data objects ! 1. The environment block 2. The command tail 3. File Control Block 1 4. File Control Block 2 ENVIRONMENT BLOCK The documentation stated that if the ENVIRONMENT block pointer is zero, then the child acquires a copy of the parents environment which is the method that I chose. COMMAND TAIL MSDOS copies the command tail into the childs PSP at offset 0080H. This is the normal location for any switches or other pieces of information that are to be passed to the The QBNews Page 34 Volume 1, Number 2 February 2, 1990 child program. DEFAULT FCS's The default FCBs are seldom used in MSDOS versions 2 & 3 because they don't support the hierarchical file structure but some programs may use them. I point then to the default FCB locations in the parent program PSP to conserve on space. The only other thing to do is point at the program name, which MUST contain the correct extention, either COM or EXE. I take care of making it an ASCIIZ string which just means that it is a string which is ended by a HEX 0. There are a few error codes that the routine will return that may need some special attention. First, if a -1 is returned, then QEXEC has determined that a null string has been passed to the routine and it is not able to continue. The following 5 error codes are returned by DOS and the true DOS error code can be found by adding +256 to the RC. 1. -255 ( 1) Invalid function 2. -254 ( 2) File not found 3. -248 ( 8) Not enough memory 4. -246 (10) Bad environment 5. -245 (11) Bad format Any error code returned that is greater that zero is a valid error code and the appropriate action should be taken. Following the assembler listing is a short QB program that shows the necessary DECLARE FUNCTION and some examples of using the QEXEC function. **** WRITTEN FOR MICROSOFT ASSEMBLER VERSION 5.10 **** Page 60, 130 EXTRN Pascal B$ShellSetup:Far EXTRN Pascal B$ShellRecover:Far .MODEL MEDIUM,BASIC .CODE Qexec Proc Far Uses DS ES SI DI, Arg1:Ptr Jmp Start HoldRC DW 0 Pars DW 0 ;Environment segment CmdLine DW 0 ;Command line for child - length CmdLineS DW 0 ; Command line text DW 5CH ;Default FCB 1 offset Fcb1 DW 0 ;FCB 1 segment address DW 6CH ;Default FCB 2 offset Fcb2 DW 0 ;FCB 2 segment address PgmTxt DB 65 Dup(?) ;Work area to build pgm string The QBNews Page 35 Volume 1, Number 2 February 2, 1990 CmdBuf DB ? ;This will hold the length DB ' ' ;Must allow one space CmdTxt DB 80 Dup(?) ;The command line text Start: Mov CS:HoldRC,-1 ;Set RC to -1 Mov BX,Arg1 ;Get Program name length Mov CX,[BX] ; in CX Jcxz GetOut ;Error - Get out Mov SI,[BX+2] ;Address of Program string Mov DI,Offset PgmTxt ;Program text buffer Push ES ;Save ES register Mov AX,CS ;Make the ES register Mov ES,AX ;the same as the CS register Pline: Lodsb ;Get a character Cmp AL,' ' ;Is it a space? Je Pline1 ;Yes - go to next routine Stosb ;Move to buffer Loop Pline ;Do it again Pline1: Xor AX,AX ;Zero out AX register Stosb ;Make PgmTxt an ASCIIZ string Jcxz Pline2 ;No more - continue Dec CX ;Adjust for space Pline2: Mov CS:CmdBuf,CL ;Save command tail length Mov DI,Offset CmdTxt ;Command tail buffer Jcxz Tline ;No more - continue Rep Movsb ;Move command tail to buffer Tline: Inc CS:CmdBuf ;Add one to tail length Mov AL,13 ;CR value in AL Stosb ;Move to buffer Pop ES ;Restore ES register Mov DI,Offset CmdBuf ;New command tail Mov CS:CmdLine,DI ;Move address to parameter block Mov DI,CS ;Get CS register address Mov CS:CmdLineS,DI ;Move address to parameter block Mov AH,62H ;Get current PSP segment Int 21H ;DOS interrupt Mov CS:Fcb1,BX ;Move PSP address to param block Mov CS:Fcb2,BX ;Move PSP address to param block Call B$ShellSetup ;QB routine to compress memory Mov DX,Offset PgmTxt ;DX points to program name ;string Mov BX,Offset Pars ;BX points to child environment Push DS ;Save DS register Mov AX,CS ;Put CS register address Mov ES,AX ; into the ES register Mov DS,AX ; into the DS register Mov AH,4BH ;Function 4B - EXEC function Mov AL,00H ;Sub Function 0 - load & exec pgm Int 21H ;DOS interrupt Pop DS ;Restore DS register Jnc Cont ;No Error - Continue The QBNews Page 36 Volume 1, Number 2 February 2, 1990 Mov AH,-1 ;Set AH = FFh Jmp Cont1 ;Error - Get out Cont: Mov AH,4DH ;Function 4D - Get child errorlevel Int 21H ;DOS interrupt Xor AH,AH ;Zero out AH register Cont1: Mov CS:HoldRC,AX ;Set RC to -1 Call B$ShellRecover ;QB routine to uncompress memory GetOut: Mov AX,CS:HoldRC ;Set the return code Ret ;Go back to caller Qexec Endp End A little explanation should be given to the following routine as it contains some routines from my QB4BAS.LIB. The first routine, QREPLACE changes all occurances of one character to another character. This is used to change all occurances of ";" to a space that I can then parse to the PATH string using QWORD and QWORDS in a FOR loop, concatinating it to the file name and then using QEXIST to see if the file exists. If it does, I break out of the FOR loop and then invoke QEXEC. The resulting return code will be printed to the screen. If the file is not found, then the routine just exits. DEFINT A-Z DECLARE SUB QREPLACE (StringName AS STRING, _ OldStr AS STRING, _ NewStr AS STRING) DECLARE FUNCTION QWORDS% (StringName AS STRING) DECLARE FUNCTION QWORD$ (StringName AS STRING, _ BYVAL Index AS INTEGER) DECLARE FUNCTION QEXIST% (FileName AS STRING) DECLARE FUNCTION QEXEC% (PgmStr AS STRING) Tmp$ = "SDIR.COM" Work$ = ENVIRON$("PATH") QREPLACE Work$, ";", " " FOR X = 1 TO QWORDS(Work$) Work2$ = QWORD(Work$, X) + "\" + Tmp$ IF QEXIST(Work2$ + CHR$(0)) = 0 THEN EXIT FOR ELSE Work2$ = "" END IF NEXT X IF Work2$ <> "" THEN PRINT QEXEC(Work2$) END IF END This second example shows the necessary format to execute a BATCH The QBNews Page 37 Volume 1, Number 2 February 2, 1990 program. This requires that another copy of COMMAND.COM be first loaded using the /C option and then the BAT file name is passed in the command tail. DEFINT A-Z DECLARE FUNCTION QEXEC% (PgmStr AS STRING) Work1$ = ENVIRON$("COMSPEC") Work2$ = " /C THISTEST.BAT" Work$ = Work1$ + Work2$ RC = QEXEC(Work$) END [EDITOR'S NOTE] QEXEC.ASM is in QEXEC.ZIP in assembled form for thoughs without assemblers. Also included is a smmal program called ASK. This was written in QuickBASIC and PDQ and it just asks for a number (0-9) and returns it as an errorlevel.