swap() Version 3.00 October 4, 1990 Copyright (C) 1990 by Marty Del Vecchio This package (swap) includes an MS-DOS assembly-language routine that can be called from a C program. It will swap most of the current program to extended memory (supplied by an XMS driver, such as HIMEM.SYS), expanded memory (EMS version 4.0), or disk, thus freeing up more memory for DOS. It will then execute another program in its place, and re-load the original program to its original state. This allows large DOS programs to execute other programs without the original program taking up DOS memory. Table of Contents I. Introduction . . . . . . . . . . . . . . . . . . . 1 II. File List . . . . . . . . . . . . . . . . . . . . 2 III. Functional Description . . . . . . . . . . . . . . 3 A. DOS Memory Management . . . . . . . . . . . . 3 B. How swap() Works . . . . . . . . . . . . . . 4 C. Swapping to Different Media . . . . . . . . . 6 1. Extended Memory . . . . . . . . . . . . 7 2. Expanded Memory . . . . . . . . . . . . 8 3. DOS Disk File . . . . . . . . . . . . . 9 IV. Calling the swap() Routine . . . . . . . . . . . . 11 A. The swap() Return Code . . . . . . . . . . . 11 B. Program File to Execute . . . . . . . . . . . 12 C. Program Command Line . . . . . . . . . . . . 13 D. EXEC Return Code Pointer . . . . . . . . . . 13 E. Swap File Name . . . . . . . . . . . . . . . 15 V. Customizing swap() . . . . . . . . . . . . . . . . 16 A. Memory Model . . . . . . . . . . . . . . . . 16 B. Swap Locations . . . . . . . . . . . . . . . 17 C. Swap Order . . . . . . . . . . . . . . . . . 17 D. Fragmentation . . . . . . . . . . . . . . . . 18 VI. Compiler-Specific Issues . . . . . . . . . . . . . 20 A. Microsoft C 5.10 . . . . . . . . . . . . . . 20 B. Microsoft C 6.00 . . . . . . . . . . . . . . 21 C. Turbo C 2.0 . . . . . . . . . . . . . . . . . 21 1. Command Line . . . . . . . . . . . . . . 21 2. Integrated Environment . . . . . . . . . 23 D. Turbo C++ 1.0 . . . . . . . . . . . . . . . . 23 1. Command Line . . . . . . . . . . . . . . 23 2. Integrated Environment . . . . . . . . . 24 VII. Revision History . . . . . . . . . . . . . . . . . 26 VIII. Information . . . . . . . . . . . . . . . . . . . 27 I. Introduction Most DOS programmers have figured out how to load and execute another DOS program from inside their own program (using the DOS EXEC system call). Almost invariably, they discover a rather large problem: with their original program loaded in memory, there is little memory left over in which to run the child program. Often, there is not enough memory to run even a DOS shell. This package provides a solution to that problem. The swap() routine will swap the original program to extended memory, expanded memory, or disk, then free up that memory. It will then execute the DOS program specified by the caller. When that program terminates, the original program is loaded back into memory, and it continues execution. By default, the swap() routine will attempt to swap the current program to extended memory; if that fails, it will try expanded memory; if that fails, it will try a DOS disk file. By re-assembling the routine in (SWAP.ASM), the programmer can specify which combination of the above media to attempt to swap to, and in which order the swapping shall be attempted. Details are provided in Chapter V. These routines have been tested with DOS versions 3.30 and 4.01. They have been tested with Turbo C version 2.0, Turbo C++ version 1.0, Microsoft C version 5.10, and Microsoft C version 6.00. Please see Chapter III, part B, for information about special problems with Microsoft C 6.00. All of these compilers comply with the Microsoft DOS standard segment naming and ordering convention (called DOSSEG in MASM). If your C compiler does not support this convention, please contact me: these routines should be adaptable to any compiler. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 1 II. File List The following files should be found in this release: SWAP.DOC This file. Documentation for use of the swap() routine. SWAP.ASM The assembly-language source file for the swap() routines. Requires the Microsoft Macro Assembler version 5.10 or the Turbo Assembler 1.0 (or above). SWAP.H Header file for SWAP.ASM. Contains function prototypes and constant definitions needed to use swap(). SWAPS.OBJ swap() assembled (MASM 5.1) for Small memory model. SWAPM.OBJ swap() assembled (MASM 5.1) for Medium memory model. SWAPC.OBJ swap() assembled (MASM 5.1) for Compact memory model. SWAPL.OBJ swap() assembled (MASM 5.1) for Large memory model. SWAPTEST.C Sample program (Microsoft C 6.00) demonstrating use and features of swap(). SWAPTEST.MSC Make file (Microsoft MAKE.EXE) for creating SWAPTEST.EXE in the large memory model, using Microsoft C 6.00. SWAPTEST.OBJ SWAPTEST.C compiled with Microsoft C 6.00, Large memory model. SWAPTEST.EXE Sample program compiled in the large memory model with Microsoft C 6.00 (SWAPTEST.OBJ, SWAPL.OBJ). MSC.ZIP Sample programs specific to Microsoft C compiler, version 5.10 and 6.00: SWAPTEST.C Sample program source file SWAPTEST.MSC Make file for SWAPTEST.EXE (enter "make swaptest.msc") SWAP?.OBJ SWAP.ASM assembled with MASM 5.1 for all four memory models. TC2.ZIP Sample programs specific to Turbo C, version 2.0: SWAPTEST.C Sample program source file SWAPTEST.TC2 Make file for SWAPTEST.EXE (enter "make -fswaptest.tc2") SWAP?.OBJ SWAP.ASM assembled with TASM 1.0 for all four memory models. TCP.ZIP Sample programs specific to Turbo C++, version 1.0: SWAPTEST.C Sample program source file SWAPTEST.TCP Make file for SWAPTEST.EXE (enter "make -fswaptest.tcp") SWAP?.OBJ SWAP.ASM assembled with TASM 1.0 for all four memory models. WHATS.NEW A short text file describing features added in this version of swap(). SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 2 III. Functional Description A. DOS Memory Management The functionality of swap() depends on the simplicity and predictability of the DOS memory management system. The same system that was introduced in DOS version 1.0 in 1981 is still in use in DOS 4.01, released in 1989. Basically, DOS has 640K of memory to manage: the hex addresses from 0000 to 9FFF (understanding DOS segments is not a requirement of this package!). Once DOS is loaded, there is one large block of free memory. In front of this block is a 16-byte header called a Memory Control Block (MCB), which contains information about the block such as how large it is. Whenever a DOS program is loaded and executed, DOS allocates part of this block of memory and assigns it to the program. The program's block of memory gets its own MCB, and the remaining memory gets another MCB. DOS functions 48 hex (allocate memory block), 49 hex (free memory block), and 4A hex (change size of memory block) all affect the chain of memory blocks maintained by DOS. Here is a map of what happens when FATHER.EXE, a mythical example program, is loaded into DOS and then executes a child program (CHILD.EXE): Before executing CHILD Address While executing CHILD +------------------------+ 0000 +------------------------+ + + + + + Used by DOS, TSRs + + Used by DOS, TSRs + +------------------------+ 2000 +------------------------+ + + + + + FATHER.EXE, the + + FATHER.EXE + + current program + + + +------------------------+ 4000 +------------------------+ + + + + + DOS Free Memory + + CHILD.EXE, the + + + + current program + + + + + + + 6000 + + + + + + + + + + + + + + + + + + + + +------------------------+ + + + + + + + DOS Free Memory + + + + + + + + + +------------------------+ 9FFF +------------------------+ SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 3 As you can see, when CHILD.EXE is executed, DOS splits up the large "DOS Free Memory" block into two smaller blocks--one used by CHILD, and the other still marked as free. And notice that while CHILD is running, FATHER is still occupying a large block of DOS memory. This puts a limit on the amount of memory available to CHILD. In this picture, since there is free memory immediately after CHILD, CHILD can call DOS system function 4A hex to increase the size of its memory block. A program can always decrease the size of its memory block, as long as it no longer accesses information outside of its block. And finally, a program can call DOS system function 48 hex to allocate a new block of memory, if any DOS free memory exists. B. How swap() Works As stated above, DOS memory management is very predictable. The swap() routine takes advantage of this predictability in the following manner. Assume that the FATHER.EXE program above uses swap() to execute CHILD.EXE. 1) swap() takes the contents of the memory block that FATHER occupies and saves it outside of DOS memory (to extended memory, expanded memory, or disk). In addition, swap() saves the contents of whatever other DOS memory blocks FATHER owns. 2) swap() calls DOS system function 4A hex to shrink the size of FATHER's memory block. It makes this block as small as possible while still keeping the swap() routine in memory. 3) swap() calls DOS system function 4B hex to execute the CHILD.EXE program (as specified by the caller). 4) When CHILD.EXE terminates, swap() again calls DOS system function 4A hex, this time to restore FATHER's block to the same size it was before swap() was called. In addition, swap() calls DOS function 48 hex to re-allocate the extra DOS memory blocks that FATHER owned. 5) Finally, swap() retrieves the original contents of FATHER's memory blocks (saved in step 1) and restores the FATHER.EXE program to its original state. The key here is step 4. We assume that the CHILD program is not a Terminate and Stay Resident (TSR) program, and that when it terminates, the memory it occupied is again free. If this is the case, the DOS function 4A hex will predictably let us grow our memory block back to its original size, and DOS function 48 hex will predictably let us re-allocate whatever extra DOS memory blocks FATHER had allocated. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 4 Following are maps of what happens in the above process: Before FATHER While CHILD is After CHILD ends executes CHILD executing and FATHER is restored +-------------------+-------------------+-------------------+ 0000 + DOS, TSRs, etc. + DOS, TSRs, etc. + DOS, TSRs, etc. + +-------------------+-------------------+-------------------+ 2000 + FATHER.EXE + FATHER (swap code)+ FATHER.EXE + + (original +-------------------+ (restored + 3000 + size) + + to original + + + CHILD.EXE + size) + + + + + + + + + +-------------------+ +-------------------+ 6000 + + + + + DOS Free Memory + + DOS Free Memory + + + + + + +-------------------+ + + + + + + + DOS Free Memory + + + + + + + + + + + + + + + + + + + + + + + + + + +-------------------+-------------------+-------------------+ There are several restrictions on use of the swap() routine, but the bottom line is that swap() lets you free all but about 2 kilobytes (depending on compiler, memory model, and swap() features) of your program's memory for use by another program. Guidelines for using swap() are described in Chapter IV. The swap() routine has been tested with and should work for all memory models with the following compilers: Turbo C 2.0 Turbo C++ 1.0 Microsoft C 5.10 Microsoft C 6.00 With the exception of Microsoft C 6.00, the above compilers all produce DOS executable programs that use only one DOS memory allocation block. Whenever the program needs more memory (for dynamic memory allocation, for example), the C library routines call DOS function 4A hex to expand the size of its block. With Microsoft C version 6.00, however, things are not always as simple. For the Small and Medium memory models, the above rule applies. But for the Compact and Large memory models (with multiple data segments), Microsoft C 6.00 allocates extra DOS memory blocks with DOS function 48 hex. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 5 These extra blocks are used for such things as holding a copy of the environment, the far heap (fmalloc()), and a buffer for printf. In addition, if your program directly allocates DOS memory blocks (_dos_allocmem() in Microsoft C, allocmem() in Turbo C), you will face the same problem. Starting with version 3.00, swap() will handle this type of DOS memory fragmentation, and will provide maximum available memory for the executed program. The swap() routine accomplishes this by saving the contents of these extra blocks, then using DOS function 49 hex to free them. When the child program is loaded and executed, all these free blocks are combined into one large block. When the child program terminates, swap() resizes the main program block as usual, then re-allocates these extra blocks with DOS function 48 hex. The swap() routine then restores the contents of these blocks. Under almost all circumstances, this process works smoothly and reliably (again due to the predictability of the DOS memory management strategies). However, under some circumstances, this procedure can fail, and the program cannot be re-loaded successfully. When swap() allocates the blocks after the execution of the child program, the address returned by DOS must match EXACTLY the original address of the block. In most cases, this occurs, and everything is fine. However, if for some reason DOS returns a different address, the program restore fails. As stated above, this is rare, but it can happen. About the only way to force this to happen is to load several Terminate-and-Stay-Resident (TSR) programs, and then unload the first one, leaving a hole in the DOS memory chain. When swap() calls DOS to allocate a block after the swap, DOS may see this hole and use it, instead of using the original address. In this case, the program cannot be reloaded. To reiterate: if you use Microsoft C 6.00 (Compact or Large model), or if your program allocates extra DOS memory blocks, then you need this fragmentation feature of swap(). There is an extra risk of your program not being re-loaded after execution of the child program, but this risk is very small. As usual, it is up to you to decide whether to use this feature or not. See Chapter V, Section D for details on how to enable and disable this feature. C. Swapping to Different Media The swap() routine can be configured to swap your program to extended memory, expanded memory, or disk. This behavior can be customized, as discussed in Chapter V. This section details how swap() deals with each media. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 6 1. Extended Memory Extended memory is a term used to describe memory available on 80286- and 80386-based personal computers that is not DOS memory. On such a PC, the first megabyte of memory (0K to 1024K) is used for DOS memory, system BIOS, adapter BIOS and memory, etc. Memory above 1024K is called extended memory. It is generally not used by DOS, which is designed to run on the 8086 processor, which can only address the first 1024K of memory. Until recently, there had been no standard way of allocating, addressing, and using this memory. Access to extended memory depended to some extent on a vendor's hardware design and BIOS implementation. The main problem was ownership of this memory--there was no standard way that one program (such as a disk cache) could tell another program that it owned a block of extended memory. Recently, however, the eXtended Memory Specification (XMS) was released by Microsoft and other companies. The XMS spec described a driver that controlled all of extended memory, and defined a programming interface to that driver that applications could use to allocate, use, and free extended memory. Microsoft ships such a driver (called HIMEM.SYS) with its Windows programs, and it is generally available on bulletin boards. In order for swap() to use extended memory, HIMEM.SYS (or an equivalent) must be loaded. If a system has extended memory, but is not running HIMEM.SYS, swap() will not be able to see the memory, and cannot use it. When swap() tries to use XMS extended memory, it first checks to see if HIMEM.SYS (or an equivalent) is loaded. It does this by calling the multiplex interrupt (2F hex) with 4300 hex in AX. If HIMEM.SYS is loaded, it returns 80 hex in AL. Once swap() confirms that HIMEM.SYS is loaded, it calculates how much extended memory it needs, and calls XMS function 09, allocate extended memory block. If this call fails (there is not enough extended memory free), the swap to extended memory fails. If the allocate call succeeds, swap() will call XMS function 0B hex, move extended memory block. It sets up a request packet to copy the contents of the current program's DOS memory block (and the contents of that program's extra DOS blocks, if any) into the extended memory block it has just allocated. The swap() routine will then shrink its program's DOS memory block (and free its extra DOS blocks), call the DOS EXEC function to execute the requested program, and restore the DOS memory block to its original size (and re-allocate the extra DOS blocks). Details on this are found in Chapter III, Section A. Once the executed program terminates, swap() again calls XMS function 0B hex to copy the contents of the program from extended memory back into the DOS memory blocks. Finally, XMS function 0A hex is called to free the allocated extended memory, and swap() returns to the caller. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 7 At this point, the original program resides in DOS memory just as it did before the call to swap(), and swap() has completely cleaned up its usage of XMS extended memory. 2. Expanded Memory Expanded memory is a special kind of memory that can be added to any PC, but that does not exist in the processor's address space. Lotus, Intel, and Microsoft (LIM) defined a specification of how to provide expanded memory in a PC and how to access it from an application program. The original specification was called LIM EMS 3.2, and it was updated to version 4.0 later. NOTE: Previous versions of swap() (2.11 and before) were able to use EMS version 3.2 or EMS version 4.0. Due to the extra complications encountered with Microsoft C 6.00 and multiple DOS memory blocks, swap() version 3.00 and later cannot use EMS version 3.2. This version of swap() must have EMS version 4.0 in order to swap to expanded memory. If you have an expanded memory board (such as the Intel Above Board or the AST Rampage Plus), and your EMS driver only supports EMS version 3.2, please contact your card's manufacturer for an updated EMS driver. Almost every manufacturer has upgraded its software by now to support EMS 4.0. Expanded memory is provided in a system in 16-kilobyte (16K) blocks called pages. An application can allocate and free any number of pages in units referred to by handles. When the application wants to copy data to or from a page, it can either ask the EMS driver to map that page into the PC's address space and do a memory copy, or it can use EMS 4.0 function 57 hex to perform the memory copy. Swap() version 3.00 and later use this latter method of transferring data to and from expanded memory, and thus must have an EMS 4.0 driver. When swap() tries to use EMS memory, it first checks to see if an EMS driver is loaded. It does this by looking at interrupt vector 67 hex. The string "EMM0XXXX" should be found 13 bytes after that vector address. If it is, an EMS driver is loaded. Once swap() confirms that an EMS driver is loaded, it calculates how many 16K expanded memory pages it needs, and calls EMS function 43 hex, "allocate expanded memory pages." If this call fails (there is not enough expanded memory free), the swap to expanded memory fails. If this call succeeds, swap() will copy the contents of the current program memory block (and its extra DOS blocks) to the expanded memory using EMS 4.0 function 57 hex, "move memory region". The EMS driver will perform the actual transfer of data from DOS memory to expanded memory. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 8 The swap() routine will then shrink its program's DOS memory block (and free its extra DOS blocks), call the DOS EXEC function to execute the requested program, and restore the DOS memory block to its original size (and re-allocate the extra DOS blocks). Details on this are found in Chapter III, Section A. Once the executed program terminates, swap() again calls EMS function 57 hex to copy the data from expanded memory back to its original location. Finally, EMS function 45 hex is called to free the allocated expanded memory, and swap() returns to the caller. At this point, the original program resides in DOS memory just as it did before the call to swap(), and swap() has completely cleaned up its usage of EMS expanded memory. 3. DOS Disk File The swap() routine can also use the DOS file system to temporarily save the contents of a program. Although swapping to a disk file is much slower than swapping to extended or expanded memory, it is just as effective. The name of the disk file to swap to is provided by the caller as a parameter (see Chapter IV). The swap() routine will call DOS function 3C hex to create the file (or truncate it if it already exists). The file will be hidden to provide a small amount of protection for the file. If this create fails, the swap to disk fails. Once the file is created, the swap() routine follows a procedure similar to the one outlined above in the description for swapping to EMS. The original program will be swapped in 32K blocks to the disk file, with the data being written with DOS function 40 hex. If any call to this function fails, it most likely means that the disk is full. If this happens, swap() will delete the file, and the swap to disk fails. Otherwise, swap() will continue to write 32K blocks until the entire program is saved. The swap() routine will then shrink its program's DOS memory block (and free its extra DOS blocks), call the DOS EXEC function to execute the requested program, and restore the DOS memory block to its original size (and re-allocate the extra DOS blocks). Details on this are found in Chapter III, Section A. When the executed program terminates, swap() will call DOS function 3D hex to open the swap file. If the file is not there, swap() cannot restore the original program. If this is the case, swap() will print an error message to the screen and terminate the program. If the file is there, swap() will simply read 32K chunks from the disk file into the DOS memory block using DOS function 3F hex. It will do this until the entire contents of the original program are restored. It will then call DOS function 41 hex to delete the swap file. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 9 At this point, the original program resides in DOS memory just as it did before the call to swap(), and swap() has completely cleaned up its usage of DOS file system. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 10 IV. Calling the swap() Routine The swap() routine is designed to be called in a C program. This is the function prototype for swap(): int swap (char *program_name, char *command_line, char *exec_return, char *swap_fname); For example, to execute a DOS command shell and display a directory of the C: drive, you would call swap() this way: swap_ret = swap ("C:\\COMMAND.COM", "/C dir c:", &exec_ret, "swap.fil"); When building a program with swap(), you should put the swap() object module as early in the linker list as possible. See Chapter VI for detailed information on using swap() with various compiler versions. The swap() routine also returns an int return code to the caller. Following are descriptions for this return code and each of these parameters. A. The swap() Return Code The swap() function returns an integer signifying the success or failure of the swap and execute. There are four possible return codes, defined in SWAP.H and here: 0: SWAP_OK Success--current program swapped, new program executed, and original program restored. 1: SWAP_NO_SHRINK Unable to shrink DOS memory block size. This indicates an error in the DOS Memory Control Block chain. This is unlikely. 2: SWAP_NO_SAVE Unable to save the program to any one of extended memory, expanded memory, or disk (depending on which functions were assembled). The new program was not executed. 3: SWAP_NO_EXEC Unable to execute the new program. If swap() returns this code, the parameter exec_return (see below) contains the DOS error code. In addition to these return codes, there is another type of error that can occur in swap() that cannot be returned to the caller. If swap() is unable to restore the original program after calling the DOS EXEC function, it cannot return to the caller, because the caller no longer exists in DOS memory! This error can occur for various reasons. If the program was swapped to disk, and the disk file was erased by the executed program, swap() has SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 11 nothing to restore. In addition, errors encountered in the XMS or EMS driver can cause this. If this happens, swap() takes the only recourse it can. It prints the following message to the screen (the standard error location): SWAP: Unable to restore program. It then calls the DOS terminate function (4C hex) and returns 255 (FF hex) as the return code. This code can be queried using the DOS ERRORLEVEL function. For example, take this batch file: Echo About to execute program that demonstrates swap(): swaptest if errorlevel 255 echo ERROR--swap() was unable to restore program! B. Program File to Execute The first parameter, program_name, is a pointer to a null-terminated string that contains the full path and file name of the program to be executed. For example, if you wanted to execute a program called TEST.EXE which is located in C:\UTIL, you must call swap() this way: swap_ret = swap ("C:\\UTIL\\TEST.EXE", "", &exec_ret, "swap.fil"); The swap() routine will NOT perform any of the following functions: -- Search the DOS PATH environment variable for a program to execute -- Redirect input or output with "<" or ">" -- Execute a batch file These functions cannot be performed by swap() because they are functions of the DOS command processor (COMMAND.COM), and not of the DOS EXEC function that swap() uses. In order to do any of these things, you must explicitly invoke the command processor with the "/C" parameter, which tells COMMAND.COM to execute the following command. For example, to execute a DIR command and redirect the output to "DIR.OUT", you would call swap() this way: char *comspec; comspec = getenv ("COMSPEC"); swap_ret = swap (comspec, "/C dir >dir.out", &exec_ret, "swap.fil"); The getenv() function is available in Turbo C and Microsoft C to search the current environment for a string. We use it here to determine where the DOS command processor is. When passing the program name parameter to swap(), remember that the "\" character, used by DOS as a directory and file name separator, is used by C to indicate an escape character. Thus, to specify the file C:\UTIL\TEST.EXE, you must pass it as "C:\\UTIL\\TEST.EXE". SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 12 The program to be executed MUST NOT be a Terminate and Stay Resident (TSR) program. If it is, swap() will not be able to grow the original program's DOS memory block back to its original size, and thus the original program cannot be re-loaded. Finally, remember that the name of the program file to be executed can be up to 127 characters (not including the null byte) long. It is the responsibility of the caller to ensure that the file name is not longer than 127 characters. C. Program Command Line The second parameter passed to swap() is the command line for the program to be run. It is a pointer to a null-terminated string that can be between 0 and 127 characters (not including the null byte). This string should NOT include the name of the program to be executed--that should be passed as a separate parameter as described above. For example, to call PKZIP.EXE and have it store files in TEST.ZIP, you would call swap() this way: swap_ret = swap ("C:\\UTIL\\PKZIP.EXE", "-r -P test.zip *.*", &exec_ret, "swap.fil"); It is the responsibility of the caller to ensure that the command line parameter string is not longer than 127 characters. In version 2.10 of swap(), code was added to parse the command line into two File Control Blocks (FCBs), which are then passed to the executed program. This more closely emulates the way COMMAND.COM loads and executes a program. This behavior has no effect on the contents of the command line. This code was added because some programs (such as DOS' CHKDSK.COM) assume that the first two command-line parameters will be parsed into the FCBs, and they use them. If you tried to execute CHKDSK.COM with a version of swap() earlier than 2.10, CHKDSK would report "invalid drive". This is because the command line parameter had not been parsed into the FCB. Code to parse the command line parameters into default FCBs was written and generously provided by David E. Jenkins. D. EXEC Return Code Pointer The exec_return is a pointer to a char (8-bit value) where swap() will return information from the DOS EXEC function. What is stored in this location depends on whether the swap() routine was successful or not. If swap() is successful (and returns 0, SWAP_OK described above), exec_return contains the return code of the executed program. This is the SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 13 same value used in the DOS ERRORLEVEL comparison. For example, if you execute PKUNZIP.EXE, and it gives the return code 0, meaning success, the byte pointed to by exec_return will be set to 0 by swap(). If swap() is unsuccessful when trying to execute the new program (and returns 3, SWAP_NO_EXEC), swap() will place the DOS error code returned by EXEC in this value. According to the DOS technical reference manual, this code will be one of the following (as defined in SWAP.H): 0x01: BAD_FUNC Bad DOS function number--unlikely 0x02: FILE_NOT_FOUND File not found--couldn't find program_name 0x05: ACCESS_DENIED Access denied--couldn't open program_name 0x08: NO_MEMORY Insufficient memory to run program_name 0x0A: BAD_ENVIRON Invalid environment segment--unlikely 0x0B: BAD_FORMAT Format invalid--unlikely Here is an example of how to handle error codes: char swap_ret, exec_ret; swap_ret = swap ("C:\\UTIL\\PKUNZIP.EXE", "D:TEST.ZIP", &exec_ret, "swap.fil"); switch (swap_ret) { case SWAP_OK: printf ("Successful, program returned %d.", (int)exec_ret); break; case SWAP_NO_SHRINK: printf ("Unable to shrink DOS memory block."); break; case SWAP_NO_SAVE: printf ("Unable to save program."); break; case SWAP_NO_EXEC: printf ("EXEC call failed. DOS error is: "); switch (exec_ret) { case BAD_FUNC: printf ("Bad function.\n"); break; case FILE_NOT_FOUND: printf ("File not found.\n"); break; case ACCESS_DENIED: printf ("Access denied.\n"); break; case NO_MEMORY: printf ("Insufficient memory.\n"); break; case BAD_ENVIRON: printf ("Bad environment.\n"); break; case BAD_FORMAT: printf ("Bad format.\n"); break; } break; } SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 14 E. Swap File Name The final parameter, swap_fname, is a pointer to a null-terminated string that contains the name of a DOS file to swap the program to. This is only needed if the version of swap() you are using will try to swap to disk (see Chapter V). If your version of swap() does not swap to disk at all, you can pass the null string (""). This parameter will only be used if swap() actually does try to swap to disk. This file name need not be a complete drive, directory, and path name. If the drive is not specified, it will be placed on the current drive; if the directory is not specified, it will be placed in the current directory. It is up to the caller to choose a safe file name for swapping. You should not choose the name of a file that already exists, as that file's contents will be lost when swap() truncates it. Because of this, you should be very careful not to specify the same file name for two different programs that use swap(). For example, if you have a program called A.EXE which uses swap to call program B.EXE, which uses swap() to call program C.EXE, you must choose different swap file names. If you use the same name (such as "C:\\SWAP.FIL") for both, you will have a problem. When A.EXE executes B.EXE, c:\swap.fil will be created, and will contain the contents of A.EXE. When B.EXE executes C.EXE and swaps to c:\swap.fil, the original contents of the file will be erased and replaced with the contents of B.EXE. When C.EXE terminates, c:\swap.fil will be read and deleted. Then when B.EXE terminates, the swap() routine in A.EXE will not find c:\swap.fil, and cannot reload A.EXE. Note: Swap() is unable to swap a program to a file that is located on a Novell Netware file server. Swap() will work with most networks (such as Microsoft LAN Manager, Network OS, etc.) that use the standard Microsoft Redirector. This is because swap() uses DOS function 60 hex (somewhat undocumented) to generate a full drive, path, and file name of the swap file. Microsoft Redirector networks are supported by this DOS function, but Novell Netware is not. However, swap() can always save the program to a file located on a local disk, even if that PC is running Novell Netware. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 15 V. Customizing swap() As stated above, the default configuration for swap() is a Small memory model function that attempts to swap the main program block AND whatever extra DOS memory blocks the program has allocated, in the following order: 1) XMS extended memory 2) EMS expanded memory 3) DOS disk file You can use the included source file (SWAP.ASM) and the Microsoft Macro Assembler (MASM) version 5.10 or the Turbo Assembler (TASM) version 1.0 or later to create a customized version of the swap() routine. To assemble the default version of swap(), you would execute MASM this way: masm swap /mx; If you are using TASM, you must instruct it to emulate MASM: tasm swap /mx /JMASM51; This will create SWAP.OBJ. You must include "/mx" on the MASM command line to tell the assembler to maintain the case of all variables and functions declared there. This allows the C program to access these items. This chapter describes how to create a customized version of swap(). A. Memory Model The swap() routine supports four different C memory models, as defined by Microsoft C and Turbo C. The memory model specifies the number of segments for code and data, and therefore the size of a code or data pointer. These memory models are: Small One code segment, one data segment Medium Multiple code segments, one data segment Compact One code segment, multiple data segments Large Multiple code segments, multiple data segments The swap() source file (SWAP.ASM) can be configured to support any of these memory models with a command-line parameter to MASM. The parameter is "/D" followed by one of the following definitions: _small Small memory model _medium Medium memory model _compact Compact memory model _large Large memory model Please note that these definitions have changed from earlier versions of swap(). Earlier versions (2.01 and earlier) did not have the underscore SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 16 before the name. This caused problems with Turbo Assembler, which uses those definitions for other purposes. With these new definitions, you can use Turbo Assembler to assemble SWAP.ASM. To do this, you must tell TASM to emulate the Microsoft Assembler (with /JMASM51, as described above). For example, to create a Large-model version of swap() with TASM, you would assemble SWAP.ASM this way: tasm swap /D_Large /JMASM51; This will create SWAP.OBJ that supports the Large memory model. Case does not matter when specifying the memory model. If no model is specified, the Small model is assumed. In addition to the above memory models, the Huge model should also be supported by assembling swap() for the Large model. I have done cursory tests for this case, but success is by no means guaranteed. B. Swap Locations The swap() routine will swap a program to extended memory, expanded memory, or disk file. If you do not want swap() to swap to all of these locations, you can re-assemble a custom version. However, it is more flexible to allow swap() to try all three locations, and the extra code needed is minimal (about 1700 bytes total). This is also accomplished with the "/D" switch to MASM, followed by one of the following: "xms" for extended memory, "ems" for expanded memory, and "disk" for disk file. For example, to assemble a version of swap() that only attempts to swap to extended memory, you would say: masm swap /Dxms; To assemble a version of swap() that attempts to swap to extended or expanded memory, you would say: masm swap /Dxms /Dems; C. Swap Order By default, swap() attempts to swap the original program to extended memory; if that fails, to expanded memory; and if that fails, to disk. Naturally, it will only try each location if that location was specified during assembly of SWAP.ASM (see Section B above). If you want to change the order in which swap() attempts to save the program, you must change the source file SWAP.ASM manually. Towards the end of the file, there is a routine called save_program. It contains three blocks of code: SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 17 ; ******************************************************************** IFDEF USE_XMS IF1 %out -- XMS extended memory ENDIF call save_xms ; Try saving to XMS extended memory jnc save_ok ; Carry clear == success, all done ENDIF ; ******************************************************************** ; ******************************************************************** IFDEF USE_EMS IF1 %out -- EMS expanded memory ENDIF call save_ems ; Try saving to EMS expanded memory jnc save_ok ; Carry clear == success, all done ENDIF ; ******************************************************************** ; ******************************************************************** IFDEF USE_DISK IF1 %out -- DOS disk file ENDIF call save_disk ; Try saving to DOS disk file jnc save_ok ; Carry clear == success, all done ENDIF ; ******************************************************************** These blocks are separated by lines of asterisks. If you wanted swap() to try expanded memory, then extended memory, then disk, you would move the middle block (IFDEF USE_EMS to ENDIF) before the middle block. This is easily accomplished with most text editors. D. Fragmentation Starting with swap() version 3.00, fragmented DOS memory allocation is handled. See Chapter III, Section D for information on this problem. By default, swap() supports swapping of multiple DOS blocks. If you want to disable this feature, you must add the parameter NoFrag to your MASM or TASM command line. For example, to disable fragmentation support when building a large-model version of swap(), you would enter: tasm /D_Large /JMASM51 /mx /DNoFrag swap.asm, swapl.obj; Although disabling this feature saves some memory, the amount saved (less than 100 bytes) is not really worth the price of possible memory SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 18 shortages. However, the option is included to allow the programmer complete control over the swap() routine. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 19 VI. Compiler-Specific Issues The source file for the swap() routine, SWAP.ASM, supports the four major memory models (Small, Medium, Compact, and Large) of the following compilers: Microsoft C 5.10 Microsoft C 6.00 Turbo C 2.0 Turbo C++ 1.0 This chapter provides information on optimizing swap() for use with each memory model of each compiler. The basic guideline for linking swap() into your executable is this: link the swap() object module (SWAPS.OBJ, etc.) into your executable as early as possible. Swap() can only swap out the modules that follow it in the executable, so putting it as early as possible allows for maximum memory for the executed program. The following sections provide specific information for each compiler. A. Microsoft C 5.10 The file MSC.ZIP, included in this distribution, contains example files for building SWAPTEST.EXE with Microsoft C 5.10 and 6.00 (using MASM 5.10). A make file called SWAPTEST.MSC is included that automates the process of building SWAPTEST.EXE. To make SWAPTEST.EXE, enter the following at the DOS command prompt: make swaptest.msc Following is a list of issues with Microsoft C 5.10: 1. Make sure the .CODE declaration in SWAP.ASM looks like this: IF @codesize .CODE SWAP_TEXT ELSE .CODE ENDIF 2. Make sure the object file that contains swap() is listed as the first object file when linking your executable: link $(LINKDEFS) swapl + swaptest, swaptest, swaptest; This ensures that your executable will take up as little memory as possible when it is executing a child program using swap(). 3. Unless your program directly allocates extra DOS memory blocks (either by calling DOS function 48 hex or by calling SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 20 _dos_allocmem()), you can get by without using the fragmentation feature of swap() ("/DNoFrag"). However, the memory savings will be minimal, and this is not recommended. B. Microsoft C 6.00 The file MSC.ZIP, included in this distribution, contains example files for building SWAPTEST.EXE with Microsoft C 5.10 and 6.00 (using MASM 5.10). A make file called SWAPTEST.MSC is included that automates the process of building SWAPTEST.EXE. To make SWAPTEST.EXE, enter the following at the DOS command prompt: make swaptest.msc Following is a list of issues with Microsoft C 6.00: 1. Make sure the .CODE declaration in SWAP.ASM looks like this: IF @codesize .CODE SWAP_TEXT ELSE .CODE ENDIF 2. Make sure the object file that contains swap() is listed as the first object file when linking your executable: link $(LINKDEFS) swapl + swaptest, swaptest, swaptest; This ensures that your executable will take up as little memory as possible when it is executing a child program using swap(). 3. If you are using the Compact or Large memory model, you must use the fragmentation feature of swap() in order to provide maximum memory for the executed program. If you specify "/DNoFrag" on the MASM command line, you disable this feature, and your child program will not have as much memory as it could. C. Turbo C 2.0 Turbo C 2.0 provides two ways to build an executable program: a command-line environment (using TCC.EXE and TLINK.EXE), and an integrated development environment. The file TC2.ZIP, included in this distribution, contains example files for building SWAPTEST.EXE with Turbo C 2.0, both command-line and integrated environments. 1. Command Line SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 21 You can build SWAPTEST.EXE with TCC.EXE (Turbo C 2.0), TASM.EXE (Turbo Assembler 1.0), TLINK.EXE (Turbo Linker 2.0), and MAKE.EXE (Turbo Make 2.0). A make file called SWAPTEST.TC2 is included to automate the process of building SWAPTEST.EXE. To do this, enter the following at the DOS command prompt: make -fswaptest.tc2 Following is a list of issues with Turbo C 2.0 command line: 1. Make sure the .CODE declaration in SWAP.ASM looks like this: IF @codesize .CODE _TEXT ELSE .CODE ENDIF 2. With the Turbo Linker (TLINK.EXE), you must explicitly list all object modules, including the C startup code (c0?.obj). In the Small and Medium models, you must list the startup module before the swap module. For example, to create a small-model version of SWAPTEST.EXE, you would use this TLINK.EXE command line: tlink c:\lib\c0s swaps swaptest, swaptest, swaptest, c:\lib\cs.lib If you specify swaps before c:\lib\c0s, the Turbo Linker gets confused, and generates an executable file that will always report "Null Pointer Assignment" at program termination. A null pointer assignment has not necessarily occurred, but the message will be printed anyway. With the Medium and Large models, it is OK to list the swap() object module first: tlink swapl c:\lib\c0l swaptest, swaptest, swaptest, c:\lib\cl.lib This will generate no spurious "Null Pointer Assignment" messages. 3. Unless your program directly allocates extra DOS memory blocks (either by calling DOS function 48 hex or by calling allocmem()), you can get by without using the fragmentation feature of swap() ("/DNoFrag"). However, the memory savings will be minimal, and this is not recommended. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 22 2. Integrated Environment Following is a list of issues with Turbo C 2.0 integrated environment: 1. Make sure the .CODE declaration in SWAP.ASM looks like this: IF @codesize .CODE _TEXT ELSE .CODE ENDIF 2. In the Integrated Development Environment (IDE) of Turbo C 2.0, you use project files (*.PRJ) to specify the source files and object modules used to build an executable program. You should always list the swap() object module on the first line of this file: swaps.obj swaptest.c (swap.h) This is demonstrated in the file SWAPTEST.PRJ included in the file TC2.ZIP. 3. Unless your program directly allocates extra DOS memory blocks (either by calling DOS function 48 hex or by calling allocmem()), you can get by without using the fragmentation feature of swap() ("/DNoFrag"). However, the memory savings will be minimal, and this is not recommended. D. Turbo C++ 1.0 Turbo C++ 1.0 provides two ways to build an executable program: a command-line environment (using TCC.EXE and TLINK.EXE), and an integrated development environment. The file TCP.ZIP, included in this distribution, contains example files for building SWAPTEST.EXE with Turbo C++ 1.0, both command-line and integrated environments. 1. Command Line You can build SWAPTEST.EXE with TCC.EXE (Turbo C++ 1.0), TASM.EXE (Turbo Assembler 2.0), TLINK.EXE (Turbo Linker 3.0), and MAKE.EXE (Turbo Make 3.0). A make file called SWAPTEST.TCP is included to automate the process of building SWAPTEST.EXE. To do this, enter the following at the DOS command prompt: make -fswaptest.tcp SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 23 Following is a list of issues with Turbo C++ 1.0 command line: 1. Make sure the .CODE declaration in SWAP.ASM looks like this: IF @codesize .CODE _TEXT ELSE .CODE ENDIF 2. With the Turbo Linker (TLINK.EXE), you must explicitly list all object modules, including the C startup code (c0?.obj). In all memory models in Turbo C++ 1.0, you can provide maximum memory for the executed program by listing the swap() object module first on the Turbo Link command line. For example, to create a small-model version of SWAPTEST.EXE, you would use this TLINK.EXE command line: tlink swaps c:\lib\c0s swaptest, swaptest, swaptest, c:\lib\cs.lib Turbo C++ 1.0 does not exhibit the "Null Pointer Assignment" problems of Turbo C 2.0 listed above. 3. Unless your program directly allocates extra DOS memory blocks (either by calling DOS function 48 hex or by calling allocmem()), you can get by without using the fragmentation feature of swap() ("/DNoFrag"). However, the memory savings will be minimal, and this is not recommended. 4. If you are using Turbo C++ to compile a C++ program, you must take extra steps to ensure that swap() is linked into your executable successfully. This is due to the type-safe linkage feature of Turbo C++ 1.0. Turbo C++ 1.0 adds characters to the names of C functions it compiles to allow the linker to check the types of parameters being passed to it (swap() becomes @swap$qnuct1t1t1()). Because swap() is assembled by the Turbo Assembler, and not compiled by Turbo C++, you must tell the compiler not to add these characters to the swap() name when it is called from your program. This is taken care of in the SWAP.H file where the function prototype of swap() is declared. It hinges on "__cplusplus" being defined (as it always is when doing a C++ compile). This follows the standard set with the Turbo C++ 1.0 include files. 2. Integrated Environment Following is a list of issues with Turbo C++ 1.0 integrated environment: 1. Make sure the .CODE declaration in SWAP.ASM looks like this: SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 24 IF @codesize .CODE _TEXT ELSE .CODE ENDIF This is especially important when using the integrated environment of Turbo C++ 1.0. If the .CODE directive above were "SWAP_TEXT" instead of "_TEXT", the swap() routine would be linked after all of the Turbo C++ library routines (printf(), etc.). In large programs, this means that swap() would be unable to swap out large chunks of your executable, partially defeating its purpose. The ".CODE _TEXT" directive puts the swap() routine in the same segment as the Turbo C++ library routines, and if you follow the procedure in step 2 below, swap() will be able to swap out the maximum possible amount of your program. 2. In the Integrated Development Environment (IDE) of Turbo C++ 1.0, you use project files (*.PRJ) to specify the source files and object modules used to build an executable program. These project files are very different than project files from Turbo C 2.0. The swap() object module should always be the first item listed in the project file. Please consult the Turbo C++ 1.0 manual for information on creating a project file. 3. Unless your program directly allocates extra DOS memory blocks (either by calling DOS function 48 hex or by calling allocmem()), you can get by without using the fragmentation feature of swap() ("/DNoFrag"). However, the memory savings will be minimal, and this is not recommended. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 25 VII. Revision History Revision Date Comments --------------------------------------------------------------------------- 1.00 4/1/90 Initial revision. Supported swapping to expanded memory or disk. Supported Small and Medium memory models. 2.00 9/6/90 Added support for swapping to XMS extended memory. Added support for Compact, Large, and Huge memory models. Made .ASM source file configurable. 2.01 9/7/90 It's always something! SWAPTEST.LNK was missing from the release .ZIP file. SWAP.DOC did not have page numbers in the table of contents. 2.10 9/11/90 Added code to parse the command line into the default FCBs (thanks to David E. Jenkins). Changed /D[model] definitions to allow assembly with Turbo Assembler. 2.11 9/28/90 Fixed problem in SWAP.ASM (variable called cmd_pad). This prevented execution of COMMAND.COM with arguments, as the pad byte (0) is interpreted by COMMAND.COM as the end of the command line. Ooops! Also, added information about using Microsoft C 6.00. 3.00 10/4/90 Added full support for Microsoft C 6.00 large code memory models (fragmentation support). Added complete information about compiling, assembling, and linking in all supported compiler versions and memory models. Fixed error in disk restore routine--it wasn't deleting the swap file after it was done. Swap() no longer supports EMS version 3.2--EMS 4.0 and above is required. SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 26 VIII. Information The original version of swap() was released as SWAP100.ZIP on April 1, 1990. That version supported swapping to expanded memory and to disk, and only the Small memory model was supported. Since then, I have had many people call both to thank me (I'm blushing) and to request enhancements. I am grateful to these people (Norman Hamer, William Wood, David Jenkins, et al) for helping to make this product more useful. This version of swap(), like all previous (and future!) versions, is hereby released into the public domain for free use by anybody and everybody. However, all the contents of this package still remain: Copyright (C) 1990 by Marty Del Vecchio. All Rights Reserved. I am not requesting a donation from anybody who uses the contents of this package. I just ask that everybody who does use swap() realize the amount of work that went into coding it, and appreciate the fact that I fully commented and released the source code for free. If you use swap() in a commercial program, and would like it listed in this document, please contact me to let me know. Everything in this package is provided with no warranties whatsoever, express nor implied, for any functionality or fitness for a specific purpose. The author will not be held responsible for any damages whatsoever resulting from the use of this package, and will not be held responsible if the package does not perform. User beware! My home address is: Marty Del Vecchio 99 Marlboro Road Southborough, MA 01772 My home phone number is: (508) 485-9718 My internet mail address is: marty@bsn.mceo.dg.com My main bulletin board is: Channel 1 BBS Boston, MA (617) 354-8873 SWAP.DOC Copyright (C) 1990 by Marty Del Vecchio 27