Jeff Prosise
Jeff Prosise writes extensively about DOS programming and is a contributing editor to several computer magazines. He is the author of DOS 5 Techniques and Utilities (Ziff-Davis Press, 1991).
QI’m using a derivation of the _Is386 function printed in the DOS Q&A column (Vol. 6, No. 6) for sensing a 386 or 486 and I’m having trouble getting it to work. The difference in my code is that I’m calling your routine from MicrosoftÒ Macro Assembler 5.1 rather than from C. It works fine as long as it’s run on a 386 or 486, but it locks up on a 286. I’ve enclosed a copy of my source code. Can you tell me what’s wrong?
AYou bet. The pertinent part of your source code is found in Figure 1.
If the CPU is a 386 or higher, the JNE instruction at the end of the listing should branch to the label IS386. I assume that’s where your 32-bit code would begin. If the branch did not occur, you’d fall back to 16-bit instructions so the program could run on other CPUs or abort the program altogether.
Here’s the problem. Because of the .386 directive at the top of the listing, which must be there if you’re to use 32-bit 386 instructions later on, MASM assembles the JNE instruction at the end as a near conditional jump rather than a short conditional jump. In simpler terms, MASM encodes the JNE instruction as a 386-only instruction. The 386 supports both the short conditional jump and the near conditional jump, but the 286 supports short conditional jumps only. The destination for a short conditional jump must lie within 127 bytes of the beginning of the instruction following the jump. The destination for a near conditional jump can lie just about anywhere, because the displacement portion of the instruction may be encoded as a 16- or 32-bit value. When the 286 tries to execute the JNE instruction, it will hang.
There are a couple of ways to work around this. The easiest is to add a SHORT operator to the JNE instruction to force a short conditional jump. Afterward, the instruction would look like this:
jne short is386
SHORT ensures that MASM will encode the conditional jump with an 8-bit relative displacement, which the 286 and 386 are perfectly capable of executing. When writing 32-bit 386 code, you can conserve a few bytes of memory by using the SHORT operator whenever the destination for a conditional jump can be reached with an 8-bit displacement. You can do the same for unconditional forward jumps when an 8-bit displacement field is sufficient. This latter principle applies to all IntelÒ CPUs, not just the 386.
Figure 1 Problematic Code
.386
code segment para use16 public 'code'
assume cs:code,ds:code
main proc near
pushf ;Copy FLAGS to BX and
pop ax ;toggle bit 14 of the FLAGS
mov bx,ax ;register
and bx,4000h
xor ax,4000h
push ax
popf
pushf ;Copy FLAGS to CX and
pop ax ;toggle bit 14 of the FLAGS
mov cx,ax ;register again
and cx,4000h
xor ax,4000h
push ax
popf
cmp bx,cx ;Compare BX and CX
jne is386 ;Branch if not equal
.
.
.
is386: [Begin 32-bit instructions]
.
.
.
QHow can an application use HIMEM.SYS to write data to extended memory and then read it back again? Because HIMEM.SYS is a part of the MS-DOSÒ operating system version 5, building in the capability for data-intensive programs to utilize XMS memory when present would seem to make a lot of sense.
AXMS memory is extended memory that has been placed under the control of HIMEM.SYS or another XMS-compliant memory manager. Before you can use HIMEM.SYS to transfer data to or from XMS memory, you must make sure that HIMEM.SYS is installed. You can do so by executing an INT 2FH with AX set to 4300H. If, on return, AL is equal to 80H, HIMEM.SYS is installed.
The next step is to obtain the HIMEM.SYS entry-point address--a far pointer to the routine you call to invoke HIMEM.SYS programming functions. You get the address by calling INT 2FH again, this time with AX set to 4310H. On return, ES:BX will contain the entry-point address.
With the entry-point address in hand, you now have the capability to call any of the functions supported by the XMS driver. Function 09H (Allocate Extended Memory Block) lets you allocate an extended memory block (EMB) for your program’s use. To call it, place the function code (09H) in AH and the size of the requested EMB in KB in DX. Then execute a far call to the driver’s entry-point whose address you obtained earlier. On return, AX=01H indicates the call was successful and AX=00H indicates it was not. If the call succeeded, DX contains the EMB handle. If the call failed, BL holds an error code. Most likely, the error code will be A0H, which indicates that there isn’t enough extended memory available to satisfy your request. You can determine the size of the largest free EMB with XMS function 08H, which returns the size of the largest block (in KB) in AX and the total amount of free XMS memory (again in KB) in DX.
XMS function 0BH (Move Extended Memory Block) is your vehicle for reading and writing information in EMBs. It can be used to move data from conventional or upper memory (the region from 640KB to 1MB) to an EMB, from an EMB to conventional memory or upper memory, between EMBs, or between locations in conventional or upper memory. Function 0BH takes a function code in AH (0BH) and a far pointer to a parameter block in DS:SI as input. The parameter block contains five fields: the length of the block to be transferred in bytes (doubleword); the handle for the source block (word); the offset into the source block (doubleword); the handle for the destination block (word); and the offset into the destination block (doubleword). If a handle is 0, the offset into the corresponding block is interpreted as a physical segment:offset address. On return, AX=01H indicates the transfer succeeded. Your program should deallocate the EMB with function 0AH (Free Extended Memory Block) before terminating.
Figure 2 illustrates how these steps are implemented in code. First, INT 2FH verifies that HIMEM.SYS is installed and gets its entry-point address. Next, function 09H allocates an EMB that is 32KB in length. Finally, function 0BH is called to copy 8000H bytes (32KB) of data from local memory to the EMB. The parameter block whose address is passed to function 0BH in DS:SI is defined with a STRUC directive and declared as a variable with the name PB. Before the call to function 0BH, PB.BLOCK_LEN is set to the length of the block of data to be transferred. PB.SRC_HANDLE is set to 0, indicating that the PB.SRC_OFFSET contains a physical address in memory, and PB.DST_HANDLE is set equal to the EMB handle obtained with function 09H.
Next, PB.DST_OFFSET is set to 0, indicating that the destination for the data is the base (offset 0) of the EMB. At the end, the value returned in AX is checked and the program branches to an error handler if AX is 0. The data in the EMB can later be read back in simply by swapping the data placed in PB.SRC_HANDLE and PB.SRC_OFFSET with the data in PB.DST_HANDLE and PB.DST_OFFSET, and then calling function 0BH again.
While this looks simple, it’s that way only because HIMEM.SYS does most of the work. Accomplishing this same task without the services of HIMEM.SYS would be much more involved. You’d have to rely on other means such as the BIOS Move Extended Memory Block (INT 15H, function 87H) service, or the undocumented LOADALL instruction to move the data. But why bother when HIMEM.SYS is available to do the work?
Figure 2 Accessing XMS Memory with HIMEM.SYS
parmblock STRUC
block_len dd ? ;Block length
src_handle dw ? ;Source handle
src_offset dd ? ;Source offset
dst_handle dw ? ;Destination handle
dst_offset dd ? ;Destination offset
parmblock ENDS
pb parmblock <> ;Parameter block
xmm dd ? ;Entry-point address
handle dw ? ;EMB handle
.
.
.
mov ax,4300h ;See if HIMEM.SYS
int 2Fh ;is installed
cmp al,80h
jne error
mov ax,4310h ;Get HIMEM.SYS entry-
int 2Fh ;point address
mov word ptr xmm,bx
mov word ptr xmm[2],es
mov ah,09h ;Allocate 32K EMB
mov dx,32
call xmm
or ax,ax
jz error
mov handle,dx ;Save the EMB handle
mov word ptr pb.block_len,8000h ;Set up the
mov word ptr pb.block_len[2],0 ;parameter
mov pb.src_handle,0 ;block
mov word ptr pb.src_offset,offset block
mov word ptr pb.src_offset[2],ds
mov ax,handle
mov pb.dst_handle,ax
mov word ptr pb.dst_offset,0
mov word ptr pb.dst_offset[2],0
mov ah,0Bh ;Move data from local
mov si,offset pb ;memory to EMB with XMS
call xmm ;function 0Bh (Move Extended
or ax,ax ;Memory Block)
jz error
.
.
.
QHow does the QBasicÔ SCREEN statement determine whether or not a given video mode is supported? I know it doesn’t simply infer a set of video modes from the type of video adapter it detects in the system, because the Quarterdeck VIDRAM utility fools the QBasic program into thinking that certain higher-order graphics modes (specifically, those that use the A000H segment of memory for video buffering) cannot be used. And it can’t test for the video mode by trying to switch to it, because INT 10H, function 00H does not return any code indicating success or failure. What’s its secret?
AYou’re right: QBasic1 does do more than simply look at what type of video adapter is installed. But there’s no secret, really. If you run it on a VGA adapter, QBasic calls INT 10H, function 1BH (Return Functionality/State Information) in the video BIOS to determine what video modes are supported. Function 1BH called with BX equal to 00H accepts a pointer to a 64-byte scratch buffer in ES:DI (BX holds the implementation type code; currently, 00H is the only implementation type that the BIOS recognizes). On return, the first four bytes of the buffer hold a far pointer to a 16-byte data structure called the static functionality table.
QBasic uses the first three bytes of this table to decide what video modes it can and cannot switch to. Each bit in these bytes corresponds to one video mode (see Figure 3). If the bit is set, the corresponding video mode is supported. If the bit is 0, then it isn’t. Normally, all the bits except for those that correspond to modes 08H, 09H, and 0AH (supported on the PCjrÔ only) and modes 0BH and 0CH (reserved for the BIOS) will be set for a VGA, because a VGA supports the full range of video modes regardless of the type of monitor it is attached to. VIDRAM fools QBasic into thinking that certain video modes are not supported by clearing the bits that correspond to those modes in the static functionality table.
If you want to use this video-sensing technique in your own programs, keep in mind that not all video adapters support function 1BH. To test whether your video adapter supports this function, execute an INT 10H with AL and BX set to 0 and ES:DI pointing to a 64-byte scratch buffer. If AL returns set to 1BH, function 1BH is supported on the current video hardware. If the function is not supported, you must determine what type of video adapter is installed and infer from that which video modes are supported. The first step would be to test for an EGA adapter by executing an interrupt 10H with AH equal to 12H and BL equal to 10H. If, on return, BL is still equal to 10H, there is no EGA adapter present, and you may proceed to test for other adapters such as CGAs and MDAs. If BL returns not equal to 10H indicating that an EGA adapter is installed, then to find out what video modes it supports, you must determine whether the EGA adapter is attached to a color or monochrome monitor. You can do this by inspecting the value returned in BH. If the adapter is configured for color, BH will return set to 00H; if it’s configured for monochrome use, BH will return 01H. A monochrome EGA adapter supports video modes 07H and 0FH. A color EGA adapter supports all modes, 00H through 10H, with the exception of modes 07H and 0FH (and 08H through 0CH).
A final note that might interest you is that on almost all VGA adapters, the static functionality table whose address is provided by function 1BH is stored in ROM. If you want to modify it as the VIDRAM utility does, you must copy it to RAM first. You’ll then have to change the pointer returned by function 1BH so that it points to the duplicate table you created in RAM, and not to the ROM-based original.
Figure 3 Format of Bytes 0-2 of the VGA Video BIOS Static Functionality Table
Byte 0 |
Bit 7 |
Bit 6 |
Bit 5 |
Bit 4 |
Bit 3 |
Bit 2 |
Bit 1 |
Bit 0 |
Byte 1 |
Bit 7 |
Bit 6 |
Bit 5 |
Bit 4 |
Bit 3 |
Bit 2 |
Bit 1 |
Bit 0 |
Byte 2 |
Bits 4-7 |
Bit 3 |
Bit 2 |
Bit 1 |
Bit 0 |
QI’m shopping for a pair of routines that I can use in C and assembly-language programs to convert relative filenames (for example, MSJDIR) to absolute filenames that include drive and path specifications (for example, C:\MSJDIR). Are there such routines?
AIf you’re programming in Microsoft C version 6.0 or later, the routine you want is built into the Microsoft C Run-Time Library. It’s called _fullpath, and it’s prototyped in the header file STDLIB.H. To convert a relative filename to an absolute filename, you pass _fullpath the address of a buffer of type char, the address of the relative filename, and an unsigned integer denoting the size of the buffer. On return, the buffer holds the fully qualified filename. If the buffer is too small to hold the resulting filename, or if an error occurs in the conversion, _fullpath returns NULL. Otherwise, it returns the address of the buffer to indicate that the buffer holds a valid result.
If you’re working in assembly language, you can either create your own routine to convert a relative filename to an absolute filename or use the undocumented MS-DOS function 60H, which debuted in MS-DOS2 version 3.0. Function 60H takes as input a pointer to the relative filename in DS:SI and a pointer to a buffer in ES:DI. On return, the buffer holds the fully-qualified version of the filename. Carry set means an error occurred and the result is invalid. The following shows how to use function 60H.
mov ah,60h ;Function code in AH
mov si,offset partial_name ;Point DS:SI to partial
mov ds, seg partial_name ;path name
mov di,offset full_name ;Point ES:DI to buffer
mov es,seg full_name ;for the full path name
int 21h ;Execute the call
jc error ;Branch on error
.
.
.
One advantage of function 60H is that it sees through aliases created by SUBST, ASSIGN, and JOIN. It also works with network drives and understands . and .. to symbolize the current and parent directories. However, because function 60H is not documented in the MS-DOS Programmer’s Reference, it may not be supported in future versions of MS-DOS. Therefore, use it with the understanding that you may still have to come up with your own code someday to perform the conversion.
1For ease of reading, "QBasic" refers to the MS-DOS QBasic programming system. "QBasic" is a trademark that refers only to this product.
2For ease of reading, "MS-DOS" refers to the MS-DOS operating system. "MS-DOS" is a registered trademark that refers only to this product.