Looking through Ralph Brown's interrupt list,
I discovered that there are many different ways to output text characters to the screen.
The ROM BIOS API offers these functions:
- AH=09h – Write Character and Attribute at Cursor Position
- AH=0Ah – Write Character Only at Cursor Position
- AH=0Eh – Teletype Output
- AH=13h – Write String
The DOS API offers the following functions:
- AH=02h – Write Character to Standard Output
- AH=06h – Direct Console Output
- AH=09h – Write String to Standard Output
What do these functions do? How do I call them? And how do I choose between them?
All of the forementioned functions are unique in what they accomplish, but
at first the abundance does seem somewhat exagerated.
Int 21h AH=02h Write Character To Standard Output
This function interprets the character codes 7 (Beep), 8 (Backspace), 9 (Tab),
10 (Linefeed), and 13 (Carriage return). All other character codes are
displayed.
Backspace is nondestructive meaning that the cursor moves one position to the
left without erasing what is underneath. Backspacing stops at the left edge
of the screen.
Tabs are expanded by this function. Tab expansion is the process of replacing
ASCII 9 by a series of one or more spaces (ASCII 32) until the cursor reaches
a column position that is a multiple of 8.
Linefeed moves the cursor one line down, scrolling the screen if required.
Carriage return moves the cursor to the far left of the screen.
Int 21h AH=06h Direct Console Output
Very similar to function 02h, but not well suited for general use as it is
not possible to output character 255. FYI a legal character in FAT filenames.
It would seem that it solely exists to avoid ctrlC/
ctrlBreak checking.
Int 21h AH=09h Write String To Standard Output
The string version of function 02h, but with the inability to output character
36 since that one is used as the string terminator. This is a major drawback
since character 36 ($) not only is a well-known currency symbol, but also
a legal character in FAT filenames.
Int 21h AH=40h Write To File Or Device
When used with predefined handle 1 this function outputs to STDOUT which
defaults to the screen. So here's one more possibility for displaying
characters. Note however that it does not rely on a string terminating
character but rather a length. Certainly the most complete output function.
It interprets what needs to be interpreted, it does not exclude certain
characters and it allows to catch redirection errors although the latter involves the use of an Int 24h handler.
Int 10h AH=09h Write Character And Attribute At Cursor Position
None of the character codes gets interpreted, they're all displayed on screen.
In text mode the provided attribute byte produces both foreground- and
background colors, but in graphics mode you can only get a foreground color.
The cursor position does not change. Pity! 1
Int 10h AH=0Ah Write Character Only At Cursor Position
Similar to function 09h in text mode but omitting the attribute byte.
In graphics mode this function is identical to function 09h.
Int 10h AH=0Eh Teletype Output
This function interprets the character codes 7 (Beep), 8 (Backspace),
10 (Linefeed), and 13 (Carriage return). All other character codes are
displayed. Too bad this function does not expand tabs!
Int 10h AH=13h Write String
To some degree this is the string version of function 0Eh. However for
general use the lack of tab expansion is certainly a limitation.
And why so many parameters? 2
Which function to choose then entirely depends on what kind of program you're
writing. Basically there's a choice between a console application and a full
screen application. Tools like CHKDSK.EXE or TREE.COM are console applications.
Programs like QBASIC.EXE or NE.COM are full screen applications.
A console application:
- does not care about using color
- writes its output on the screen in a linear fashion
- does not hinder the OS's capability for output redirection
- usually performs a single task
- often terminates in the blink of an eye
A screen oriented application:
- benefits greatly from using the right amount of color
- wanders over the screen and writes what it wants where it wants it
- needs not to worry about output redirection as the above will soon enough
render such output unreadable
- often performs (too) many tasks
- goes on until you decide it's time to exit
Console Application
Only the DOS output functions offer the required/recommended redirection
capability. Output function 02h is perfect. Even though it lacks its own error
reporting for when an error (very unlikely) should occur while output is
redirected, the default critical error message of "Abort, Retry, Fail?"
doesn't look too much out of place. (Had this been a full screen application,
the same message would have disrupted the screen enormously.)
; IN (ds:si) OUT ()
WriteStringDOS:
pusha
jmps .b
.a: mov dl,al
mov ah,02h
int 21h ;DOS.DisplayCharacter -> AL
.b: lodsb
test al,al
jnz .a
popa
ret
Sometimes however you'll want to display a temporary item like:
- a prompt of some kind ("-- More --", "Strike a key when ready...", etc. )
- a running counter/percentage
- a progression bar
In order to avoid messing up any redirected output, it's best to not use DOS
output functions on these temporary items. Better use the WriteStringBIOS
code that comes next.
Full Screen Application
Now output redirection is your enemy, so don't use any of the DOS output functions.
If you don't need the color then next code snippet is for you. It basically
adds tab expansion to the BIOS Teletype function.
; IN (ds:si) OUT ()
WriteStringBIOS:
pusha
mov bx,0007h ;Display page 0, Color if graphics mode
jmps .b
.a: cmp al,9
je .Tab
mov ah,0Eh
int 10h ;BIOS.Teletype
.b: lodsb
test al,al
jnz .a
popa
ret
.Tab: mov ax,0E20h ;Start displaying space(s)
int 10h ;BIOS.Teletype
mov ah,03h
int 10h ;BIOS.GetCursor -> CX DX
test dl,7
jnz .Tab ;Column not yet multiple of 8
jmps .b
Most of the time a bit of color will work wonders. Following code snippets use
BIOS function 09h for outputting the colored character and BIOS function 0Eh to
advance the cursor. A good combination that keeps things simple.
Use the first one in text video mode:
; IN (bl,ds:si) OUT ()
WriteStringWithAttributeTVM:
pusha
mov bh,0 ;Display page 0
jmps .d
.a: cmp al,9
je .Tab
cmp al,13
ja .b
mov cx,1101_1010_0111_1111b
bt cx,ax
jnc .c ;7,8,10,13 don't need the color
.b: mov cx,1
mov ah,09h
int 10h ;BIOS.WriteCharacterAndAttribute
.c: mov ah,0Eh
int 10h ;BIOS.Teletype
.d: lodsb
test al,al
jnz .a
popa
ret
.Tab: mov cx,1 ;Start displaying colored space(s)
mov ax,0920h ;ASCII 20h is space character
int 10h ;BIOS.WriteCharacterAndAttribute
mov ah,0Eh
int 10h ;BIOS.Teletype
mov ah,03h
int 10h ;BIOS.GetCursor -> CX DX
test dl,7
jnz .Tab ;Column not yet multiple of 8
jmps .d
Use the second one in 16 color graphics video mode. It's a bit more involved
since BIOS refuses to draw background colors.
; IN (bl,ds:si) OUT ()
WriteStringWithAttributeGVM:
pusha
mov bh,0 ;Display page 0
mov bp,bx
jmps .d
.a: cmp al,9
je .Tab
cmp al,13
ja .b
mov cx,1101_1010_0111_1111b
bt cx,ax
jnc .c ;7,8,10,13 don't need the color
.b: push ax
mov cx,1
mov bx,bp
shr bl,4 ;Get background color (high nibble)
mov ax,09DBh ;ASCII DBh is full block character
int 10h ;BIOS.WriteCharacterAndAttribute
xor bx,bp ;Anticipate upcoming 'xor'
and bl,15 ;Get foreground color (low nibble)
or bl,128 ;Have BIOS 'xor' it
pop ax
.c: mov ah,0Eh
int 10h ;BIOS.Teletype
.d: lodsb
test al,al
jnz .a
popa
ret
.Tab: mov cx,1 ;Start displaying colored space(s)
mov bx,bp
shr bl,4 ;Get background color
mov ax,0EDBh ;ASCII DBh is full block character
int 10h ;BIOS.Teletype
mov ah,03h
int 10h ;BIOS.GetCursor -> CX DX
test dl,7
jnz .Tab ;Column not yet multiple of 8
jmps .d
In summary
- For a console application the WriteStringDOS and WriteStringBIOS
procedures are more than adequate3.
- For a full screen application the WriteStringWithAttributeTVM and
WriteStringWithAttributeGVM procedures equally deliver3
4.
- Neither DOS nor BIOS are sufficiently equipped to handle the graphics video
modes. Either write your own graphics routines (not a trivial task!) or use
a 3rd party graphics library.
1 Long delayed feature request: Making the cursor advance upon
receiving a replication count of zero.
2 Retorical, not an actual question.
3 Unless you select a video mode for which the BIOS has no TTY
support. eg. Many BIOSes can't scroll when in a VESA video mode. I even came
across a BIOS that is unable to write characters with function 09h on the legacy graphics video mode 12h!
4 Writing directly in the video memory is possible but requires more
effort.