  Being able to swap programs in and out of memory, either to extended, 
expanded, or disk, has always been a topic of interest to many programmers. 
For C and Pascal, there are a variety of different swap utilities and 
libraries that you can try. For QuickBASIC, on the other hand, there have 
only been one or two, the most notable being MicroHelp's SuperShell program 
(which is now included with their new "Muscle" library).
  Lately, a file has been going around for C that will swap the program to 
XMS (eXtended Memory Specification), EMS (Expanded Memory Specification), or 
to disk. It leaves about a 2K kernal in memory that will swap the program 
back into main memory when the child process that was run is finished. It's 
found under the name SWAPxxx.ZIP, where the "xxx" is replaced by the version 
number. It is currently up to version 3.00 (SWAP300.ZIP). Since it is 
freeware, it requires no money to use. Also, the author was kind enough to 
include full source code so it could be modified as necessary. I have 
successfully modified it to work with QB programs. Below is a list of the 
necessary changes that must be made.
  NOTE: You must have MASM 5.1 or later, QuickAssembler 2.xx, or Turbo 
Assembler running in MASM mode for the following to work!


  Step 1)  At the very top of the SWAP.ASM file, right under the "page" 
statement, put the following 2 lines:

     .Model Medium, BASIC
     .Code


  Step 2)  Delete the "DOSSEG" line that appears about halfway through the 
large comment block at the top of the file.

  Step 3)  Delete the entire first "IFDEF" block that appears right after 
the comments. It deals with model declarations, and we have already added 
that at the top of the file.

  Step 4)  Delete all text from the beginning of the comment block after the 
declarations for "bptr", "wptr", and "dptr" to the ENDIF statement that 
comes before the comment block with a line of "*"'s.

  Step 5)  Find the beginning of the actual "swap" routine, and delete the 
"public swap" statement that's immediately above it.

  Step 6)  Rewrite the line that says:

"  swap            proc    prog:PTR, cmdline:PTR, return:PTR, save_file:PTR"

           to say:

"  QBSwap proc uses si di es ds, Prog:PTR, CmdLine:PTR, Return:PTR,_
Save_File:PTR"

  (NOTE: How you space the words or capitalize the letters doesn't really 
affect anything at all. Also, the "_" at the end of the first line means 
that the "Save_File:PTR" should be on the same line as everything else -- 
without the "_" being there. This is done because my editor wrapped the 
line. Also, DON'T include the quotes.)


  Step 7)  Delete the 4 "push" statements after the "QBSwap proc" line.


NOTE: For the next several steps, ignore the lines with "<Start of Code 
Cutout>" and the lines with "<End of Code Cutout>". They should NOT appear 
in the code. They are provided here only for clarity in understanding which 
section of code I'm talking about. Also, you may see that the comments are 
slightly out of line or have "..." at the end of them. This is because the 
author obviously was working in a 132-column mode on his computer.

  Step 8)  Find the following code block that comes after the "get_name:" 
label:
----------------------<Start of Code Cutout>-------------------------------

; If multiple data segments, load DS:SI from stack.  Else, just load SI
IF @datasize
                push    ds                      ; Save segment register
                lds     si, dptr prog           ; Load 32-bit far pointer
ELSE
                mov     si, wptr prog           ; Load 16-bit near pointer
ENDIF                                    ; DS:SI -> program name from caller

                mov     di, offset @code:prog_name  ; ES:DI -> our storage area
name_loop:      lodsb                           ; Fetch next byte
                stosb                           ; Save next byte
                or      al, al                  ; Was it 0 (end of string)?
                jnz     name_loop               ; No, get next one
IF @datasize
                pop     ds                 ; Pop DS if it was pushed above
ENDIF

----------------------------<End of Code Cutout>--------------------------

  And change it to:

--------------------------<Start of Code Cutout>----------------------------

     mov bx, Prog   ;Get pointer to Prog$
     mov cx, [bx]   ;Get the length
     jcxz NulStr    ;If it's a NULL string, return an error to QB

     mov si, [bx+2]  ;Get address of Prog$
     mov di, offset @code:prog_name   ;Get storage area
     rep movsb   ;Copy Prog$ into the storage area
     jmp get_cmd  ;Get the command line

NulStr:
     mov ret_code, 2  ;QBSwap returns 2 -- unable to swap program
     mov exec_ret, 0FFh ;ExecReturn% = FFh (256) -- NULL program string
     jmp Finished  ;Get out of here


-----------------------------<End of Code Cutout>--------------------------


  Step 9)  Find the following code block that comes after the "get_cmd:" 
label:
----------------------------<Start of Code Cutout>--------------------------

; If multiple data segments, load DS:SI from stack.  Else, just load SI
IF @datasize
                push    ds                      ; Save segment register
                lds     si, dptr cmdline        ; Load 32-bit far pointer
ELSE
                mov     si, wptr cmdline        ; Load 16-bit near pointer
ENDIF                                   ; DS:SI -> command line from caller
                
             mov     di, offset @code:cmd_line   ; ES:DI -> our storage area
                xor     cl, cl                  ; Keep track of length in cl

cmd_loop:       lodsb                           ; Fetch next byte from DS:SI
                or      al, al                  ; Was it 0 (end of string)?
                jz      cmd_end                 ; Yes, we're done
                stosb                           ; No, store byte
                inc     cl                      ; Increment length
                cmp     cl, MAX_DOS_CMD         ; Are we at maximum cmd length?
                jnz     cmd_loop                ; Nope, keep going

cmd_end:        mov     bptr es:[di], 0dh       ; Put CR at end of cmd line
                mov     bptr cs:cmd_len, cl     ; Store command-line length

IF @datasize
                pop     ds               ; Pop DS if it was pushed above
ENDIF

----------------------------<End of Code Cutout>---------------------------

  and change it to:

----------------------------<Start of Code Cutout>--------------------------

  mov bx, CmdLine   ;Get pointer to CmdLine$
  mov cx, [bx]  ;Get length
  cmp cx, MAX_DOS_CMD  ;Is it longer than the maximum DOS command line
                       ;length?
  jnz Continue   ;No, so continue on with our operation
  mov cx, MAX_DOS_CMD  ;Otherwise, truncate CmdLine$ to the proper length

Continue:
  mov dx, cx ;Save command-line length
  mov si, [bx+2]  ;Get address of CmdLine$
  mov di, OFFSET @code:cmd_line    ;Get address of storage area
  rep movsb  ;Copy over the string. NOTE: If the string was NULL, then this
             ;statement never executes. Consequently, our storage area is
             ;NULL as well, and we have nothing to worry about.

  mov bptr es:[di], 0dh    ;Add a CR to the end of the com. line (DOS req.)
  mov bptr cs:cmd_len, dl   ;Store command-line length

----------------------------<End of Code Cutout>-------------------------


  Step 10)  Find the following code block after the "get_file:" label:

----------------------------<Start of Code Cutout>-------------------------

; If multiple data segments, load DS:SI, else just load SI
IF @datasize
                push    ds                      ; Save segment register
                lds     si, dptr save_file      ; Load 32-bit pointer
ELSE
                mov     si, save_file           ; Load 16-bit pointer
ENDIF                                  ; DS:SI -> swap file name from caller

                mov     di, offset @code:fname  ; ES:DI -> our storage area

resolve:        mov     ah, 60h   ; DOS INTERNAL function to resolve...
                int     21h      ; Stores complete path at ES:DI--we need...
                                 ;  current drive or directory have changed
                                 ; Ignore file name error here--it
                                 ;  will be caught in save_disk if need be

IF @datasize
                pop     ds       ; Pop DS if it was pushed above
ENDIF

------------------------------<End of Code Cutout>------------------------

  and change it to:

-----------------------------<Start of Code Cutout>-----------------------

  mov bx, Save_File  ;Get pointer to SaveFile$
  mov cx, [bx]  ;Get length of SaveFile$
   ;NOTE: the next statement may be uncommented if you wish to check for a
   ;NULL SaveFile$
  ;jcxz NulStr2
  mov si, [bx+2]  ;Get address of SaveFile$
  mov di, OFFSET @code:fname   ;Get address of our storage area

   ;NOTE: the following several statements may be uncommented if you wish to
   ;support DOS 2.xx in your program

  ;mov ah, 30h    ;INT 21h Function 30h -- Get DOS Version
  ;int 21h   ;Goto DOS
  ;cmp al, 2  ;Is it DOS 2.xx?
  ;ja resolve   ;No, so use internal DOS 3.xx function
  ;rep movsb   ;Otherwise, assume that the filename & path is correct and
               ;just copy it to our storage area
  ;jmp short Continue2  ;Continue on with our routine

resolve:
   mov ah, 60h   ;DOS 3+ internal function to "Canonicalize" or
                         ;"Normalize" a path name
   int 21h   ;Goto DOS

   ;Uncomment the next several lines if you wish to return an error if
   ;SaveFile$ is NULL

   ;jmp short Continue2  ;Continue on with the routine

;NulStr2:
   ;mov exec_ret, 0FEh    ;Set the return to FEh (255) -- NULL string for
                          ;disk file
   ;mov ret_code, 2   ;QBSwap returns 2 -- unable to swap program
   ;jmp Finished  ;Get out of here

------------------------------<End of Code Cutout>-------------------------

   NOTE: Do NOT delete the "IFDEF USE_DISK" block around the above code, 
unless you're absolutely certain that you will always be supporting disk- 
swapping. After all, one of the best parts about SWAPxxx.ZIP is that you can 
configure it to swap to all 3 things or just 1 or 2.


  Step 11)  Before the next statment line, the one that reads:
  "    mov     wptr cs:ret_code, 0     ; Initialize swap's return code",
     put a "Continue2:" (without quotes, of course) line.

  Step 12)  Find the following code block near the end of QBSwap routine:

--------------------------<Start of Code Cutout>---------------------------

; Giving user exec's return code.  It could be a 16- or 32-bit pointer
IF @datasize
                push    ds
                lds     si, dptr return         ; Load 32-bit pointer
ELSE
                mov     si, wptr return         ; Load 16-bit pointer
ENDIF                                           ; DS:SI -> return code variable
                
                mov     al, bptr cs:exec_ret    ; Store exec's return code
                mov     bptr [si], al           ;  at address specified by caller

IF @datasize
                pop     ds                      ; Pop DS if pushed above
ENDIF

                pop     ds
                pop     es
                pop     di
                pop     si
                mov     ax, wptr cs:ret_code    ; Give return code
                ret

---------------------------<End of Coe Cutout>------------------------------

  and change it to:

---------------------------<Start of Code Cutout>--------------------------

  Finished:

        mov  si, wptr Return  ;Get pointer to Return%
        mov  al, bptr cs:exec_ret  ;Get return code for EXEC process
        mov  bptr [si], al  ;Put it in Return%
        mov  ax, wptr cs:ret_code  ;Get the QBSwap return code
        ret  ;Back to QB

----------------------------<End of Code Cutout>---------------------------

  Step 13)  FINAL STEP!  Change the "swap endp" line to "QBSwap endp".


  There! You now have a perfectly good routine for QB that will swap a QB 
stand-alone (LINKed with BCOMxx.LIB) .EXE program to XMS, EMS, or disk.
Have fun!

           Joey Lizzi