Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Assembly Language Step by Step 1992

.pdf
Скачиваний:
145
Добавлен:
17.08.2013
Размер:
7.98 Mб
Скачать

Positioning the Hardware Cursor

So far, in writing to the screen, we've simply let the text fall where it may. In general, this means one line of text following another, and when the screen fills DOS scrolls the screen upward to make room on the bottom line for more text. This makes for dull programs, very similar to programming in the bad old days when everything was done on clunky mechanical printers called Teletypes. (Indeed, this kind of screen I/O is called glass teletype I/O, due to its similarity to a printer scrolling paper up one line at a time.)

Let's leave the glass teletypes behind, and take control of the cursor. BIOS service 10H (often nicknamed VIDEO, in uppercase, for reasons that are obscure) offers a simple service to position the hardware cursor on the text screen. The service number is loaded into AH, a common thread through all BIOS services. The value 0 must be placed in BH unless you intend to tinker with multiple display pages. That's a story for another time; while you're learning, assume BH should be set to 0 for cursor positioning.

The new position of the cursor must be loaded into the two halves of the DX register. Cursor positions are given as XY coordinate pairs. The X component of the cursor position is the number of character columns to the right of the left margin where you want the cursor to be positioned. The Y component is the number of lines down from the top of the screen where you want the cursor to be positioned. The X component is loaded into DL, and the Y component is loaded into DH. The routine itself is nothing more than this:

GotoXY

PROC

;

Select VIDEO service 2: Position cursor

mov AH ,02H

mov BH ,0

:

Stay with display page 0

int 10H

 

;

Call VIDEO

ret

ENDP

: Return to the caller

GotoXY

 

 

Don't forget that the X and Y value must be loaded into DX by the caller. Using GotoXY is done this way:

mov

DL,35

; Pass

35 as X coordinate

mov

DH,9

; Pass

9 as Y coordinate call

GotoXY

; Position the cursor

EAT3.ASM uses GotoXY to position the cursor, but it does something else as well: it clears the display. If you're going to be moving the cursor at will around the screen with GotoXY, it makes sense to start with a completely clear screen so the remains of earlier

programs and DOS commands don't clutter up the view.

There's another VIDEO service that can do the job. VIDEO Service 6 is an interesting and powerful one: not only does it clear the screen, it can scroll the screen as well, by any specified number of lines. Furthermore, it can clear or scroll the entire screen, or only a rectangular portion of the screen, leaving the rest of the screen undisturbed.

If scrolling is unfamiliar to you, just press Enter repeatedly at the DOS prompt and watch what happens when you reach the bottom line of the screen. The displayed text on the screen jumps up by one line, and an empty line appears at the bottom of the screen. The DOS prompt is then redisplayed in the empty line. Scrolling is the process of making the screen jump up by one or more lines, and inserting one or more blank lines at the bottom as appropriate.

Using VIDEO Service 6

Understanding VIDEO service 6 involves learning a fair number of values that need to be passed to the service in registers. The one unchanging item is the service number itself, passed as 6 in register AH (as with all BIOS services).

Service 6 acts upon a rectangular region of the display. This may be the full screen, or it may be only part of the screen. You must pass the coordinates of the upper-left and lowerright corners of the region in registers CX and DX. Because screen coordinates are always smaller than 255 (which is the largest value that can be expressed in 8 bits) the register halves of CX and DX are used independently to carry the X and Y values.

The upper-left corner's X coordinate is passed in CL, and the upper-left corner's Y coordinate is passed in CH. These are 0-based coordinates, meaning that they count from 0 rather than 1. Confusion is possible here, because most high-level languages like Turbo Pascal number coordinates on the screen from 1. In other words, the upper-left corner of the screen in Turbo Pascal is given by the coordinates 1,1. To the BIOS, however, that same corner of the screen is 0,0. The width and height of a typical screen to Turbo Pascal would be 80 x 25; the BIOS would use 79 x 24.

Similarly, the lower-right corner's X coordinate is passed in DL, and the lower-right corner's Y coordinate is passed in DH. (Again, counting from 0.)

Service 6 either scrolls or clears the region. It can scroll the screen upward by any arbitrary number of lines. This number is passed to service 6 in register AL. Clearing the region is a special case of scrolling it: when you specify that zero lines be scrolled, the entire region is cleared. The full screen is actually a special case of a rectangular region. By passing the coordinates of the upper-left and lower-right corners of the screen (0,0 and 79,24) the full screen is cleared.

Procedures with Multiple Entry Points

This is a lot of versatility for one service to handle, and it brings up a couple of questions. First of all, how versatile should a single procedure be? Should there be one procedure to clear the whole screen, another procedure to clear part of a screen, and a third procedure to scroll part of the screen?

The answer is that one procedure can do all three, and not duplicate any code at all. The method involves writing a single procedure that has four different entry points. Each entry point is a label that is called with a CALL instruction. When a given entry point's label is called, execution begins at the instruction specified by that label. There is only one RET instruction, so the procedure is in fact one procedure. It's like a house with three front doors but only one back door; having three front doors does not make it three separate houses.

Here's what such a creature might look like:

ClrScr

PROC

 

mov CX,0

; Upper-left corner of full screen

ClrWin:

mov DX.LRXY

; Load lower-right XY coordinates into DX

mov AL,0

; 0 specifies clear entire region

ScrlWin:

mov BH,07H

; Specify "normal" attribute for blanked

line(s)

mov AH,06H

; Select VIDEO service 6: Initialize/Scroll

VIDEO6:

int 10H

 

; Call VIDEO

ret

 

; Return to the caller

ClrScr

 

ENDP

 

There's nothing much to this. What we have here is a collection of MOV instructions setting up values in registers before calling VIDEO through interrupt 10H. Note that all of the entry points, except the one (ClrScr) doing double duty as the procedure name, must be given with colons. The colon, as I pointed out earlier, is necessary after any label used to mark an address within a code segment.

The multiple entry points exist only to allow you to skip certain portions of the procedure that set up values that you don't want set. All the registers used by service 6 must be set up somewhere. However, they can either be set within the procedure or in the caller's code just before the procedure is called. If the procedure sets them, the # registers have to be set to some generally useful configuration (say, clearing the entire screen); if the caller

sets them, the registers can be set to serve the caller's needs, making service 6 perform any of its varied combinations.

So it is with the ClrScr procedure. If you enter ClrScr through its main or top entry point, all of its internal code will be executed: CX and DX will be set to the upper-left and lower-right corner coordinates of the full screen; AL will be set to 0 to clear the full screen rather than scroll it, and BH will be loaded with the "normal," (blank, for white text on a black background) text display attribute. Then service 6 is called.

If you wish to clear only a rectangular area of the screen (a window), you would use the ClrWin entry point. This entry point starts executing the code after CX and DX are set to the corners of the full screen. This means that the caller must load CX and DX with the upper-left and lower-right corners of the screen region to be cleared. Calling ClrWin without setting CX and DX at all will execute service 6 with whatever leftover garbage values happen to be in CX and DX. Something will happen, for certain. Whether it's what you want to happen or not is far less certain.

Keeping in mind that for proper operation, all of service 6's required registers must be set, calling ClrWin would be done this way:

mov CX,0422H ; Set upper-left corner to X=22H; Y=04H mov DX,093AH ; Set lower-right corner to X=3AH; Y=09H call ClrWin ; Call the window-clear procedure

The two MOV instructions are worth a closer look. Rather than use a separate instruction to load each half of DX and CX, the two halves are loaded together by loading a 16-bit immediate data value into the full 16-bit register. Thus two MOV instructions can do the work that a first glance might think would take four MOV instructions. This is a good example of writing tight, efficient assembler code. The trick is to document it (as I've done above) to make sure you understand six weeks from now what the magic number 093AH means!

The first instruction at the label ClrWin sets AL to 0, indicating that the region is to be cleared, not scrolled. If in fact you do want to scroll the region, you need to skip the MOV instruction that loads 0 into AL. This is the purpose of the entry point labeled ScrlWin: it gets you into the procedure below the point where you select clearing over scrolling. This means that you not only have to set the corners of the region to be scrolled, but also the number of lines to scroll as well.

mov

CX , 0422H

;

Set upper-left corner to X-22H;

Y-04H

mov

DX , 093AH

;

Set lower-right corner to X-3AH;

Y-09H

mov

AL , 1

;

Set to scroll by one line

call

ScrlWin

;

Call the window-scroll procedure

As you can see, more and more of the work is being done by caller and less and less within the procedure. How you arrange the entry points to the procedure depends on what operations get done most frequently. In my programs, I tend to clear the whole screen a lot, clear windows less frequently, and scroll windows less frequently still, and this is what I had in mind while arranging the code within ClrScr.

Note that there is no entry point to scroll the full screen. To scroll the full screen, you need to load the coordinates of the corners of the full screen into CX and DX, and then call ClrWin as though you were clearing just a portion of the screen. If you do a lot of screen-scrolling, you might define a separate routine for scrolling the full screen. As an interesting exercise, write such a routine and a program to test it.

As one more entry point, I included a label VIDEO6. This label short-circuits all of the register setups apart from loading the service number into AH. This allows you to do something odd and infrequently, like scrolling the entire screen by three lines

.

Memory Data or Immediate Data?

You may have been wondering what the variable identifier LRXY is for and where it is defined. LRXY is simply used to hold the current X,Y coordinates for the lower-right corner of the screen. Where LRXY is defined is in the program's data segment, in the usual way variables are defined, as you'll see if you look ahead to the full listing of

EAT3.ASM.

The more interesting question is why. Most of the time I've been showing you values loaded into registers from immediate data, which is often useful. The coordinates of the upper-left corner of the full screen, for example, are always going to be 0,0, and nothing will change that. The lower-right corner, however, is not necessarily always 79,24.

The original 1981-vintage IBM MDA and CGA graphics adapters are indeed capable of displaying only an 80 by 25 text screen and no more. However, with an EGA it is possible to have an 80 by either 25 or 43 text screen, and the VGA, introduced in 1987 with the PS/2 line, can display 25, 43, or 50 line screens, all 80 characters wide. The newer super VGA video boards are capable even more different text modes, some of them with more than 80 characters in a visible line. If your program can determine what size screen is in force when it is invoked, it can modify its displays accordingly.

Avoid dropping immediate values into code (we call this hard-coding) whenever you can. A better strategy, which I'll be following from now on, uses variables in the data

segment initialized with currently correct values when the program begins running.

Use Comment Headers!

As time goes on, you'll find yourself creating dozens or even hundreds of procedures as a means of not reinventing the same old wheel. The libraries of available procedures that most high-level language vendors supply with their compilers just don't exist with assembly language. By and large, you create your own.

Keeping such a list of routines straight is no easy task, when you've written them all yourself. You must document the essential facts about each individual procedure or you'll forget them, or, worse yet, remember them incorrectly and act on bad information. (The resultant bugs are often very hard to find, because you're sure you remember everything there is to know about that proc! After all, you wrote it!)

I recommend adding a comment header to every procedure you write, no matter how simple. Such a header should contain the following information:

The name of the procedure

The date it was last modified

What it does

What data items the caller must pass it to make it work correctly

What data is returned by the procedure, if any, and where it is re-turned. (For example, in register CX.)

What other procedures, if any, are called by the procedure

Any "gotchas" that need to be kept in mind while writing code that uses the procedure

;DX: The offset of the string to be displayed ; String must be terminated by "$"

;Action: Displays the string at DS:DX up to the "$" ; marker, then issues a newline. Hardware cursor ; will move to the left margin of the following

; line. If the display is to the bottom screen line, ; the screen will scroll.

;Calls: Write