ibm images.... (using BASIC) (column) Will Fastie.
mages . . . ibm images . . . ibm im
Here we go with another technical column, this time for you fans of Basic. Wait a minute, don't go away yet! Even if the word technical scares you, I think you might find the results of my work this month of some interest, and for a variety of reasons.
First and foremost, I have kept my promise to demonstrate how to interface Basic with routines in BIOS. Second, I have chosen to interface to the routine that handles scrolling of windows, since this is the only major feature of the video display system that is not directly available to the Basic programmer. Third, I have uncovered some interesting facts as a result of my work. Finally, the window handling routine is a handy one in general.
And don't be scared off by the assembly language routine printed herein: "I'll show you how to use the routine without having to own or use the assembler. What's that? Still scared? Okay, forget it. I'll see you next month.
But if you're still here, let's get to work.
Basic, of course, is the only programming language that is immediately available to the IBM PC owner. It comes "free' with the operating system. Basic is well known, and pretty easy to learn, so it is in widespread use. As a professional programming tool, it leaves much to be desired, but its combination of price and distribution are compelling.
Basic is often not well suited to the task at hand, especially if performance is an issue. This can partly be solved with the Basic Compiler, a truly valuable asset to the Basic program developer. However, there are some things which even the Basic Compiler cannot deal with, and an entirely different set of things which the language can handle, but for which assembly language provides considerably more performance. We will discuss a feature that falls in the first category, something for which assembly language is required. The feature is the control of independent "windows' on the display screen.
As you probably know, the PC is delivered with a considerable amount of software in Read Only Memory (ROM). Part of this is the resident Basic interpreter, but the other part is called BIOS, for Basic Input/Output System. The so-called "primitive' routines for handling most of the devices which can be attached to the PC are part of BIOS, including a very large section devoted to the video display devices, both color and monochrome. One of the features supported by the video I/O section is scrolling. Two routines are provided, one for scrolling up and one for scrolling down.
Scrolling is the act of moving all the lines of text on the screen up (or down) by a line (or more). The top (or bottom) lines disappear from view, as if they had passed behind the edge of the screen. The space created at the bottom (or top) of the screen is usually blanked by the scrolling operation so that the program can insert new information for display. It is easy to see the effect simply by pressing the ENTER key repeatedly at the DOS prompt: sooner or later you get to the bottom of the screen and the display at the top begins to rise and go away.
The PC version of scrolling is very sophisticated, both because it can go either up or down and because it can be applied to a rectangular window anywhere on the screen. Since the BIOS program provides only the scrolling function and not the management of windows, the application program is free to operate as many windows as are needed. And that is a vital point: your program does the real work, while BIOS provides a little bit of very fast help.
Listing 1 is the program I wrote in Basic to demonstrate how windows and scrolling work. It uses two routines, SCROLLUP and SCROLLDN, which I will describe in a minute. It allows you to give scrolling commands and watch the effect of the command on the display.
The program is clear, and instructions for its use are included in the comments. The commands are single letters, which are interpreted immediately without need of an ENTER key depression. When parameters are required, the program prompts for them; if letters, they are taken immediately, and if numbers, an ENTER is required.
When invoked, the program clears the screen, divides it into three windows, and fills the two upper windows with text. The bottom window is the command area in which the program prompts are displayed. The routines SCROLLUP and SCROLLDN are used in the program whenever the windows are to be scrolled, cleared, or filled. The reset command (R) can be used to restore the program to its initial state in case things go awry (they can).
The section of the program in lines 1200 to 1250 is the really tricky part in which the assembly language routine is loaded into memory. The first step (1210) is to specify how much memory Basic will use for its workspace. This is needed to reserve memory space outside the Basic workspace area for the assembly language program. If you are using Basic (as opposed to BasicA), the number 32000 should leave plenty of space. BasicA is bigger, so it might be necessary to specify a smaller number when it is used. In any case, the workspace size is a number which should be adjusted for your exact configuration. The second step is to load the program using BLOAD. The file created by the BSAVE command contains specific memory addresses, so line 1220 works without any additional parameters.
Lines 1230 through 1250 establish the values Basic will need to call the assembly language routines. What you have to do is specify the precise memory address for each routine loaded. Addresses for the 8088 processor have two components, the segment address and the offset within the segment. In Basic, the DEF SEG statement is used to establish the segment address, and the names of the subroutines are assigned the offset values. In this case, SCROLLUP is at location 0 in segment &H1FFC, while SCROLLDN is at location &HE. (Note: The &H is the Basic convention to denote a hexadecimal number.)
In this program the DEF SEG statement has been used once, so its "scope' is the entire program. That means that Basic statements like CALL, POKE, PEEK, and others that specify an offset against the current segment address are affected. This is not a problem for DEMO, but care must be exercised if a program of yours uses such statements to be sure that the correct address is being specified in each case. This might imply a DEF SEG before every such statement.
The two scroll routines require six arguments, of which four are the coordinates that define the position of the window on the display. Each co-ordinate is a pair, one to specify the row position and one to specify the column. The coordinates are stored in arrays named UL (upper left) and LR (lower right). The suffixed letter is either R (for row) or C (by now, you should have the idea). The first argument to the routines is the number of lines to be scrolled, and for which a value of zero means that the window is to be cleared. The last argument specifies the display attribute to be used on the blank lines that are scrolled in.
The rest of the program should be self-explanatory. Note that SCROLLUP is used as a way to clear window 3 (the command area).
I know what you are thinking: "Hey, Will, where'd that subroutine in SCROLL. ABS come from anyway?' Fair question. The answer, unfortunately, is ridiculously complex.
The first part isn't too bad. Listing 2 shows the assembly language routines SCROLLUP and SCROLLDN. Again, I think it is well-documented, so I won't overdo the explanation here. I'll make two points. First, the "struc' declaration beginning on line 36 defines a set of names that are used to reference values on the stack. I defined this structure so I wouldn't have to remember the actual numeric offset of each value; instead, the stack is referenced by expressions such as
which is the address of the number of lines argument. This simply leads to greater clarity. Second, both scroll routines are exactly alike with the exception of the function code for the BIOS call. I have therefore written a common routine that is called by both scroll routines and whose function it is to set up the registers for the call. The common routine, cleverly named "setup,' does use one trick: since the value passed is always less than 256, only the low-order byte of each integer is used.
In anticipation of testing the program with a compiled version of the DEMO program, I declared the names SCROLLUP and SCROLLDN "public,' which simply means that LINK, the DOS linker, will "see' the names when it reads SCROLL. OBJ, the object file. I used the same names in DEMO, and for the same reason. More on that later.
So now we have a program in Basic, and a routine in assembly language. How do we get them together? All my screaming and cursing did not help. The manuals did, however, and I must admit they were very clear and complete. Version 1.1 of the Basic manual includes explicit instructions for this entire process, including examples.
Nonetheless, it is a very painful process to connect an interpreted Basic program with assembly language routines. The compiled Basic program is simplicity itself; the linker does all the work. All that the linker does for the compiled program must be done by hand for the interpreted version.
The first step is to assemble the program and run LINK to produce a .EXE file. Use the /HIGH switch to force the program to reside in the high end of memory. Do not link the module with any libraries, and do not attempt to actually execute the result--you will be disappointed. A record of the process is shown in Listing 3.
Step two involves loading the program into memory with the debugger while the Basic interpreter is also resident in memory. This process is shown in Listing 4. The debugger is invoked and Basic is loaded. The registers are examined. The file SCROLL.EXE is named and then loaded into memory. The registers are then examined to see where in memory the program is. Note the contents of CS (&H1FFC) and IP (0), which are the same as the values used in DEMO. Registers SS, CS, and IP are restored to their earlier values, and the Basic interpreter is then executed by giving the DEBUG command G, for go.
When I did this, Basic emitted an error message, "Direct statement in file.' At first I was worried about this, but after I failed in my attempts to make the error go away, I decided to ignore it. I have not yet discovered why this happens, nor was it mentioned in the manual. It is possible that I should have explicitly defined the Basic workspace in the DOS command line (with the /M: switch), but because I have 128K of main memory, the assembly language routine ends up very high in memory, well beyond the limits of the biggest possible Basic workspace. Once I ignored the message, the rest of the procedure was uneventful.
When the command Go is given, Basic gets control. And once in Basic, it is possible to write any portion of memory to a disk file with the BSAVE command. The exact sequence of commands I needed was
BSAVE "SCROLL. ABS', 0,
These commands require information from the second set of registers shown in Listing 4. The value for the DEF SEG statement comes from the CS register. The offset 0 from the BSAVE command comes from the IP register, and the length &H3B comes from the CX register.
And bingo! The file SCROLL.ABS has the subroutine in a format that can be loaded by the BLOAD command, just as is done by the DEMO program. Only one thing remains, and that is to figure out how to get out of Basic, out of DEBUG, and back to DOS. Say SYSTEM to leave Basic, which in this case takes you not to DOS but to the debugger, which will report "Program terminated normally.' Then use the Q command to leave the debugger.
And don't forget, the values shown in these listings may not be for you. You must do the whole process yourself and get numbers that fit your system.
Ah yes. The Catch.
The catch is that you must have the assembler to develop and implement such routines, unless you are a master at encoding instructions into hexadecimal notation and placing them into memory by hand. The assembler isn't very much, only $100 from your dealer, so it might be a practical addition to your software library if you have many things like this to do.
If you are interested in these routines but don't have the assembler, you can still manage. It will just take more manual labor. What you have to do is use the debugger to enter the hexadecimal values into memory, and then write the section of memory to disk. You can then use a procedure similar to the one I have outlined to get the program loaded with your Basic program. Another possibility is to take the hexadecimal values and put them into DATA statements in your Basic program. You then have to write a short routine that will POKE them into some appropriate place in memory.
You must watch one thing, though. Lines 55 and 65 of Listing 2 are CALLs to the setup routine. On the listing, they are shown as if they had been encoded as E8 001C. However, you must not use the 001C value as is. It is the actual off-set in the program of the label SETUP. If you look at lines 79 and 80 you will see that value in the leftmost column. It is the linker which will fill in the actual value that is needed, which is the distance between the E8 instruction and the SETUP routine.
If the SCROLL.EXE program is in memory with the debugger, the unassemble (U) command can be used to display the contents of memory in assembly language terms, and the actual offset can be seen. The actual offsets are 22 for line 55 and 8 for line 65. Also, the offsets are stored low byte first, so line 55 should have a contents of E8 16 00 and line 65 should read E8 08 00. Note that I have shown the low-order value of these numbers first, because the 8088 expects them to be stored in memory that way.
What I craved during all this was a program that would take the .EXE file and convert it into a file to be BLOADED. Except for time, I would have done it. It is the one thing that would have eliminated all these crazy steps.
The DEMO program demonstrates not only the scrolling features, but how tremendously fast they are. It is simply not possible to see any scrolling at all; things just happen. I was not prepared for quite that much speed, and it actually caused me to change the program. At first, I had cleared the command area by writing blanks into it. This could be seen to be slow, so I defined the third window and had the scroll routine clear the area.
It is nice that the performance is so good. It made me mad, though, because the BIOS should be capable of similar speeds for writing to the display. As I have mentioned before, that process is painfully slow, a design flaw in the BIOS. The scrolling routines clearly show how quick the machine can be, particularly if you realize that the scrolling operation requires the movement of each character from one display memory location to another. Well, maybe in PC II, eh?
Leave it to me to find a bug wandering around. You can see the effect if you have DEMO running. Give the command S followed by U or D, and a number 10 or greater. The screen goes bonkers (the best word I could come up with to describe what happens).
It appears that BIOS does not check to see if the requested number of lines is bigger than the size of the window. When it is, the scroll is not right. I call this a bug because there is nothing documented to indicate that such a value is illegal and because the routine should handle all cases anyway.
One thing bugged me. I am used to using row and column coordinates that start at 1 because these are expected by the LOCATE statement. The BIOS routines want values that start at 0. This was a great hassle until I spent a few minutes marking a listing of the program and getting everything just so. You can see the problem in line 1580 of DEMO.
I lay the blame for this at the doorstep of Basic, because it is out of step with everything else. In BIOS, everything seems to start from 0, and therefore other languages tend to support those conventions. In defense of Basic, I must admit that human beings start from 1, so it is more natural.
The final program suite is useful for experimenting with windows. It is easily modified to support more windows; the set of routines forms a kernel around which a larger, more complex screen management program can be developed.
The DEMO program also includes several useful programming techniques. One is the use of the INSTR function to decode the command letter, and the subsequent use of the value to control an ON . . . GOSUB statement. Another is the statement that converts all input characters to upper case. It is interesting because it does not use "magic' numbers, and the only piece of information assumed is that the ASCII value for the letter a is numerically greater than that for the letter A.
Other routines in BIOS, and those that you might write yourself, can be interfaced to Basic using the techniques illustrated here. I hope all this information has been helpful.
Table: Listing 1.
Table: Listing 2.
Table: Listing 3.
Table: Listing 4.