BASIC And Machine Code Interfacing Mark Bridger The Boston Computer Society IBM PC Users' Group While BASIC has many deficiencies as a programming language, it is the most popular and simplest language to use. In fact, IBM chose to concentrate most of the PC's special effects in ROM BASIC. However, if one wants the features of BASIC, plus speed, control or extra effects not provided by IBM BASIC, the only alternative is to interface with assembly language. This raises the problem of how to link and call an assembly language routine from a BASIC program. Appendix C of the IBM BASIC Manual gives a rather sketchy account of this. This article will try to explain the BASIC to machine language interface procedure. Fundamentally, the problems of linkage are as follows: o Problem 1: BASIC can only call a routine which is loaded into memory somewhere. So - how to load, and where? o Answer 1: Use the BLOAD statement, and make sure that the subroutine does not overlap either BASIC or your program (more of this later). o Problem 2: You can only BLOAD something which has been BSAVEd. To BSAVE something, you must:(a) be in BASIC and (b) have that something in memory at the same time. o Answer 2: The only way to do this is by using that all- purpose tool, DEBUG. To illustrate the procedure in detail, we shall construct a small program which does something that can't be done from BASIC alone; namely, write a character in color on the graphics screen. The reader should be somewhat familiar with PC assembly language. The BASIC part of the package is the small program called "SMALL.BAS" listed in figure 1. The assembly language part is the code which contains the procedure "writechar" listed in figure 2. Notice that the BASIC program refers to the file "write.srt" as well as the procedure writechar; it is the construction of this file we will examine first. Figure 1: BASIC program SMALL.BAS 10 CLS: KEY OFF 20 SCREEN 1,0: COLOR 1,1 'blue graphics screen 30 INPUT "Choose color 0,1,2,3:";HUE% 40 HUE% = HUE% - 4*INT(HUE%/4) 50 X$ = INKEY$: IF X$ = ""GOTO 50 60 IF ASC(X$) = 0 GOTO 50 'only ASCII 70 CHAR% = ASC(X$):CLS 80 DEF SEG = &HFCO 'reserve top of memory 90 BLOAD "write.srt",0 100 WRITECHAR% = 0 110 CALL WRITECHAR%(CHAR%,HUE%) 120 END Figure 2: Assembly Language WRITE.ASM stack segment para public 'stack' stack ends cseg segment para public 'code' assume cs:c ss:stack ;procedure writechar (var char, ;hue:byte) This procedure prints a ;single character, whose ASCII code ;is char, in the color specified by ;its parameter "hue". In graphics mode ;this is one of the four palette ;colors. public writechar writechar proc far push bp ;save bp mov bp,sp ;bp points to stack push ax ;save ax push bx ;save bx push cx ;save cx push si ;save si sti ;enable interrupts mov ah,9 ;prepare to write mov si,[bp+8] ;si gets address of cha mov al,[si] ;al gets value of char mov si,[bp+6] ;si gets address of hue mov bl,[si] ;bl gets value of hue mov cx,1 ;write only once mov bh,0 ;graphics mode int 10h ;do it! pop si ;restore registers pop cx pop bx pop ax pop bp ret 4 ;pop addresses, return writechar endp cseg ends end Proceed as follows: 1. Type Figure2 under the filename WRITE.ASM. 2. Run this through the Assembler to produce the file called WRITE.OBJ. This is machine code which must be linked. As mentioned previously, we want to have the program and BASIC in memory at the same time so we can BSAVE it. To do this we must make sure that it is loaded fairly low in memory; we also want to make sure that our procedure write gets loaded 'high' in memory by DEBUG. One way to do this is to use the Linker with the 'H' option. This is done as follows. 3. Call up the Linker by typing LINK at the DOS prompt. 4. Answer its first question (for an object file) with WRITE/h and press Enter. Continue until the Linker runs out of questions. It will probably give you an error message that there is no stack segment. That's OK. It will create a file called WRITE.EXE which is what we want. For simplicity's sake, assume from now on that all files mentioned are on the same disk in drive A (the default). 5. Now for the debugger. At the A> type DEBUG BASIC.COM and press ENTER. This loads BASIC into memory. (You can also use BASICA.) To see the exact memory locations, at the DEBUG prompt, type r and press ENTER. You will see a display in hex of the contents of all the registers. 6. Important: Make a note of the contents of the segment registers DS,ES,CS,SS (they should all be the same; mine read 052A). The instruction pointer (IP) should be 0100. 7. Quit DEBUG by entering q. 8. Load the subroutine by typing DEBUG WRITE.EXE and press ENTER. 9. To look at the registers again, enter r and press ENTER. 10. Important: Make note of the contents of CS (segment location), IP (offset), and CX (length). CS should be fairly large (mine reads 4FFD), IP should be 0000, and CX should be 0030 for the file WRITE.EXE (length = 30HEX bytes = 48 bytes). 11. Now, load BASIC exactly as it would have been if WRITE.EXE had not been loaded. 12. Make sure that DS,ES,CS,SSS and IP are exactly the same as when BASIC was loaded initially. That is why you made note of the registers. DS and ES are probably OK, but CS,SS, and IP must be changed. To change CS, for example, respond to the DEBUG hyphen with r cs and press Enter. DEBUG will display a colon. You type in the correct value and enter. Fix SS and IP also. Don't leave DEBUG! 13. Reload BASIC as follows. After the hyphen type n BASIC.COM and press Enter. This names the file. Then type L and press Enter. This loads the file. If all has gone well, the two files should be loaded into disjoint parts of memory whose locations we know from the CS and IP settings. 14. Now run BASIC from the debugger. Respond to the hyphen with g=(BASIC's CS):(BASIC's IP) and press Enter. You should get a "direct statement in file" message and BASIC's familiar OK. 15. Now from BASIC we want to BSAVE our subroutine. o Set the default segment to the value of WRITE.EXE's CS register as follows: Type:def seg = &H(CS value). Then press Enter. o Think of a name for the subroutine. In the example we used the unimaginative name of WRITE.SRT. o BSAVE it as follows: bsave "name, &H(IP value), &H(CX value) (Hex numbers require the prefix'&H' in BASIC.) Let's take a brief look at the BASIC call procedure. One of the problems with BASIC is where to put its machine language program. The BASIC work segment is the first 64K bytes of memory. If you have more than 64K, you can BLOAD machine language anywhere above this segment: &H1700 is a popular choice. If you a have a color/graphics card (we're assuming you do here) you can even put programs fewer than 192 bytes at &H1F40 (between the even and odd buffers). Assuming 64K, we located the subroutine in the top 1K in step 8. To be perfectly safe, you can reserve this space by beginning the program with a CLEAR,&H8C00 command. (BASIC and DOS take up about 28K leaving 36K. Then 36K - 1K = 35K = Hex 8C00). Step 9, the BLOAD command, actually puts our subroutine in the default segment with an offset of 0 (Hex FC00). That's about it. Further details about the BASIC 'CALL' command are in the BASIC Manual. Two clearly written books about assembly language are: "The iAPX Book" (Intel) and "IBM PC Assembly Language" (L. Scanlon, Brady Publishing Co.,1983). (Editor's Note: Another excellent book on this subject is "IBM PC Assembly Language Programming" by Dave Bradley.)