MS-DOS(R)Q&A

by Jeff Prosise

Jeff Prosise writes extensively about programming in MS-DOS and is a contributing editor to several computer magazines. He is the author of two books on MS-DOS published by Ziff-Davis Press.

QI’d like to revise my TSR screen-saver so it won’t blank the screen if the MicrosoftÒ WindowsÔ operating system is running. Is it possible for an MS-DOSÒ-based application to detect if Windows1 is active so it can adjust its actions accordingly?

Stephanie Stennett
Metairie, LA

AThere are two ways for a TSR to find out if Windows is running. The first involves hooking into the MS-DOS2 multiplex interrupt (INT 2FH) and trapping occurrences of it with AX set to 1605H or 1606H. When Windows 3.0 or 3.1 starts up in standard or enhanced mode, it broadcasts a message saying so to the system via INT 2FH. Called the Windows Init Broadcast, this message is identifiable by a value of 1605H in AX. When Windows terminates, it broadcasts another message to the system letting interested parties know that Windows is about to end. This message, called the Windows Exit Broadcast, is identifiable by a value of 1606H in AX. If you want your TSR to become inactive while Windows is running, your TSR should monitor INT 2FH for broadcast messages and refrain from acting after it receives a Windows Init Broadcast message.

Figure 1 shows a sample INT 2FH handler that monitors Windows broadcast messages. It sets a flag called winflag when an init message is received and clears the flag when an exit message is received. With this mechanism in place, the TSR could check winflag at any time to determine if Windows is running.

Figure 1 INT 2FH Handler for Monitoring Windows Broadcast Messages

mplex_int proc far

pushf ;Save flags

cmp ax,1605h ;Windows init broadcast?

jne mplex1 ;Jump if not

mov cs:[winflag],1 ;Set Winflag

jmp short mplex_exit ;Exit

mplex1: cmp ax,1606h ;Windows exit broadcast?

jne mplex_exit ;Jump if not

mov cs:[winflag],0 ;Clear Winflag

mplex_exit: popf ;Restore flags

jmp cs:[int2Fh] ;Exit to original handler mplex_int

endp

The drawback to using the broadcast message method is that it only works with standard and enhanced mode Windows. That’s fine for Windows 3.1, but it’s a problem for 3.0, which also supports real mode. If real mode is a concern (and it probably should be as long as there are copies of Windows 3.0 in circulation), your program can use a method for detecting the presence of Windows that doesn’t rely on broadcast messages. Transient applications can use this method too, because a transient application cannot monitor broadcast messages when it’s not running.

This alternate method involves two steps. The first step is to execute an INT 2FH with AX set to 1600H. If, on return, AL is equal to 00H or 80H, then either Windows is not running or if it is running, it’s not in enhanced mode. If Windows 3.0 or 3.1 is running in enhanced mode, or if Windows/386Ôenvironment 2.x is running, then AL will return set to something other than 00H or 80H. A value of 01H or FFH means Windows/386 2.x is running; any other value means Windows 3.x is running in enhanced mode. If the latter is true, that is, if AL is not 00H, 01H, 80H, or FFH, then AL contains the major version number of the version of Windows that is running (3 for Windows 3.0 or 3.1) and AH contains the minor version number (0 for 3.0, 1 for 3.1).

A second step is only necessary if the first test returns 00H or 80H in AL. To test for real or standard mode Windows, a program must call INT 2FH again, this time with AX set to 4680H. On return, AX=0000H means Windows 3.x is running in real or standard mode; any other value means Windows is not running in real or standard mode. Only after completing this step can an application be certain that Windows 3.x is not running in any of its three supported modes.

Figure 2 illustrates this. The procedure wintest returns AL=0 if Windows is not running and AL=1 if Windows is running. You can use this procedure as is for assembly-language programs, or modify it so that it can be called from languages such as C. If you prefer, you can even rewrite it for C using the Microsoft C int86 function and avoid assembly language altogether.

Figure 2 Test for Real, Standard, and Enhanced Mode Windows

wintest proc near

mov ax,1600h ;Test for enhanced mode

int 2Fh

or al,al ;Branch if AL=00h

jz test2

cmp al,80h ;Branch if AL=80h

je test2

return_yes: mov al,1; ;Set AL to 1 for return

ret ;Return to caller

test2: mov ax,4680h ;Test for real and standard modes

int 2Fh

or ax,ax ;Branch if AX=0

jz return_yes

xor al,al ;Zero AL for return

ret ;Return to caller wintest

endp

There is also a documented means for using the Windows Init Broadcast to prevent standard and enhanced mode Windows from starting altogether. Although this would result in an extremely unfriendly TSR (unfriendly as far as Windows is concerned, anyway), it is a viable course of action for MS-DOS TSRs whose very presence is a threat to system integrity when Windows is running. All the TSR must do is chain the Windows Init Broadcast message down the TSR chain by calling the original INT 2FH handler. Then, when it receives control back, it can set CX to 1 and execute an IRET instruction (see Figure 3) to return from interrupt. If CX is nonzero when control returns to Windows, Windows will not start. But because Windows will not display an error message saying why it won’t start, if you write such a program, you should include code to display a message explaining to the user why he or she is unable to run Windows.

Figure 3 INT 2FH Code to Prevent Windows from Starting in Standard or Enhanced Mode

mplex_int proc far

pushf ;Save flags

cmp ax,1605h ;Windows init broadcast?

je mplex1 ;Jump if so

popf ;Restore flags

jmp cs:[int2Fh] ;Exit to original handler

mplex1: popf ;Restore flags

pushf ;Push flags

call cs:[int2Fh] ;Chain the interrupt

mov cx,1 ;Set CX to non-zero value

iret ;Return from interrupt mplex_int

endp

QIt is well known that the MS-DOS operating system sets up a chain of memory control blocks (MCBs) to define the structure of memory. The MCB was formally documented in the MS-DOS 5 Programmer’s Reference. It is also well known that MS-DOS 5 sets up a chain of MCBs in upper memory just like the one it sets up in lower memory, and that MS-DOS function 5803H can be used to link the upper and lower MCB chains together. (See MS-DOS Q&A, MSJ, Vol. 6, No. 6 for a discussion on linking upper memory and UMBs--Ed.) What I don’t know is how to determine, through software, the address of the first UMB in upper memory. Is this information documented anywhere? Better yet, is there an API call that returns the address of the lowest UMB?

John F. Stehle
Bryn Mawr, PA

AThere is no documented function call that returns the address of the lowest UMB. But you can devise a method for locating the first UMB easily enough. First, use MS-DOS function 5803H to clear the upper memory link and trace the MCB chain from beginning to end. When you reach the end of the chain (identified by the MCB whose signature byte contains a Z), remember the segment address. Then call function 5803H to set the upper memory link, and trace the MCB chain again. You’ve reached the upper memory area when you locate an MCB at a higher segment address than the MCB that formerly marked the end of the chain.

Figure 4 demonstrates this. After clearing the upper memory link and obtaining the segment address of the first MCB, this routine walks the chain of MCBs from start to finish. It stores the segment address of the final MCB in DX. Then it sets the upper memory link and walks the chain again. At each stop, it compares the address of the current MCB (held in AX) to the address saved in DX. If AX is greater than DX, then the first MCB in upper memory has been located and execution branches to the line labeled main4.

Figure 4 Locating the First MCB in Upper Memory Managed by MS-DOS

mov ax,5803h ;Clear upper memory link

mov bx,0

int 21h

mov ah,52h ;Get starting MCB address

int 21h

mov ax,es:[bx-2] ;Transfer address to AX

push ax :Save address on the stack

main1: mov es,ax ;Transfer address to ES

cmp byte ptr es:[00h],"Z" ;Branch if this is the final

je main2 ; MCB in the chain

add ax,es:[03h] ;Compute next MCB address

inc ax

jmp main1 ;Check the next MCB

main2: mov dx,ax ;Save MCB address in DX

mov ax,5803h ;Set upper memory link

mov bx,1

int 21h

pop ax ;Retrieve starting MCB address

main3: cmp ax,dx ;Branch if we’ve passed what

ja main4 ; was formerly the final MCB

mov es,ax ;Transfer address to ES

add ax,es:[03h] ;Compute next MCB address

inc ax

jmp main3 ;Check the next MCB

main4:

o

o

o

Once you’ve located the MCB that marks the beginning of upper memory, you can follow the MCB chain higher into memory to locate the first free UMB or to determine what programs and drivers are loaded in upper memory. In most systems, the first "high" MCB will be located at segment address 9FFFH, and will contain the letters SC in the owner name field (the eight bytes beginning at offset 08H in the MCB). Furthermore, the owner ID (offset 01H) will be 08H, indicating that the ensuing memory block is owned by the operating system. In fact, this is the block reserved for the video buffer. MS-DOS marks all blocks in the upper memory area reserved for hardware use with the letters SC and an owner ID of 08H.

Upper memory blocks not marked SC are free if the owner ID is zero, or allocated to a program if the owner ID is nonzero. Blocks with an owner ID of 08H and the letters SD in the owner name field are a special case. These blocks contain device drivers loaded high with the DEVICEHIGH command. One block may contain several device drivers, each residing in a sub-block defined by a secondary MCB chain inside the block marked SD. The MCBs in the secondary chain are not documented in the MS-DOS 5 Programmer’s Reference, but they are identical to other MCBs in all but one respect: in the signature field, they contain D, which stands for Driver rather than M or Z. Inspection reveals that a similar secondary chain is used in conventional memory to demarcate the space set aside for files, buffers, and other internal MS-DOS data spaces.

If you’re writing a program that tracks the MCB chain into upper memory, don’t forget that EMM386.EXE isn’t the only upper memory manager you may encounter. QEMMÔ-386 manages upper memory in its own way and requires special handling. If you boot QEMM-386 with the statement DOS=UMB in CONFIG.SYS, memory is structured just as if it were EMM386.EXE providing the UMBs. If QEMM-386 is loaded without a DOS=UMB statement, it sets up its own chain of MCBs in upper memory. The format of the MCBs is identical to the MCB format used by MS-DOS. The difference is that there is no link between this chain and the one MS-DOS sets up in conventional memory, nor can you establish a link by calling MS-DOS function 5803H with BX=1. You must obtain the address of the first MCB directly from QEMM-386.

The code fragment in Figure 5 shows how. Executing an INT 2FH with AX set to D201H, BX set to 4849H, CX set to 5241H, and DX set to 4D30H returns in CX the segment address of the first MCB in upper memory if QEMM-386 version 5.0 or later or QRAM (Quarterdeck’s memory manager for non-386 PCs) is loaded. The address is only valid if BX returns 4F4BH, which just happens to be the ASCII codes for the letters OK (not coincidentally, BX, CX, and DX hold the ASCII codes for HIRAM0 on entry to the function call; AX holds the function and subfunction codes). Once this address is obtained, a program can walk the chain of upper-memory MCBs in exactly the same way it would walk the chain of MCBs in low memory. An MCB with a signature byte of Z marks the final UMB in upper memory.

Figure 5 Locating the First MCB in Upper Memory Managed by QEMM-386

mov ax,0D201h ;Call interrupt 2Fh, function

mov bx,4849h ; D201h to get the starting

mov cx,5241h ; upper memory MCB

mov dx,4D30h ; address in CX

int 2Fh

cmp bx,4F4Bh ;Check status indicator in BX

jne no_qemm_umbs ;No UMBs if BX != 4F4Bh

mov es,cx ;Transfer MCB address

o

o

o

QHow can an MS-DOS program determine if a drive supports removable media such as floppy disks or tape cartridges?

Theresa Graffia
Nesconset, NY

AThat’s an easy one if the application is running under MS-DOS version 3.0 or later. The key is IOCTL function 4408H, Does Device Use Removable Media, which is documented in the MS-DOS 5 Programmer’s Reference. You call function 4408H with a code in BL specifying the drive you want to check (0=default, 1=A:, 2=B:, and so on), as shown here:

mov ax,4408h

mov bl,drive

int 21h

jc not_removable

or ax,ax

jnz not_removable

is_removable:

o

o

o

If the carry flag returns clear, then AX holds 0 if the drive supports removable media, and 1 if it doesn’t. Carry set means that either the drive doesn’t support this IOCTL function (in which case it is safe to assume that it also doesn’t support removable media), you passed the function an invalid drive code, or the drive is a network drive. You can identify network drives in MS-DOS 3.1 and later by calling function 4409H, Is Drive Remote.

At present, there is no documented means for determining whether a network drive supports removable media. Nor is there a documented way for a program to sense removable media under versions of MS-DOS prior to 3.0. It’s entirely possible that even MS-DOS cannot detect if a network drive supports removable media. MS-DOS uses the removable-media flag to avoid rebuilding its internal drive parameter tables each time a non-removable disk is accessed. It’s not hard to imagine that MS-DOS might take the conservative approach and assume that all network drives support removable media.

1For ease of reading, "Windows" refers to the Microsoft Windows operating system. "Windows" is a trademark that refers only to this Microsoft product.

2For ease of reading, "MS-DOS" refers to the Microsoft MS-DOS operating system. "MS-DOS" is a trademark that refers only to this Microsoft product.