File: ASMBASIC.TXT On: CLUBware Software Diskette #1 Author: Stan Radzio Copyright (C) 1984. RAYHAWK AUTOMATION, N W, INC. ASMBASIC represents an improved approach for calling assembly language routines from IBM PC BASIC. The major improvement is that this approach allows the same source code to be used by both the BASIC Interpreter and the BASIC Compiler. Hence, you can debug a program in the more productive interpreter environment and migrate them easily to the faster executing compiled environment. The published approaches in the magazines make it very difficult to use the same source to both programs. These approaches involve storing routines within BASIC arrays or in high memory beyond the reaches of the interpreter. ASMBASIC stores the assembly routines in low memory below the Basic interpreter. The required subroutines are not loaded with BLOAD or POKE; they are already resident in memory when the Basic interpreter is invoked. ASMBASIC is distributed already assembled and linked with eight assembly language subroutines: QPRINT - quickly print a string at the current location ZPRINT - print a string using the color/attribute given SCRLDN - scroll some portion of the screen down SCRLUP - scroll some portion of the screen up XREP - repeat some character along the x axis YREP - repeat some character down the y axis CLREOL - clear from the current position to the end of the line CLREOS - clear from the current position to the end of the screen The source to ASMBASIC is included on the CLUBware diskette so that these subroutines may be replaced with ones of your own choosing or so that you may add additional subroutines. The technique is demonstrated in program SUBDEMO.BAS and works like this: 1) Assemble your utility subroutines. 2) Enter the names of your assembly routines into the subroutine table within ASMBASIC. 3) The first name in the table is subroutine 1. The second name in the table is subroutine 2. Print the table so you won't forget the ordering of names. 4) Assemble ASMBASIC and link it with your assembly language routines. 5) Execute ASMBASIC to make the code resident. You need only execute ASMBASIC once after the system is booted. However, if you execute it more than once, no harm will be done. File: ASMBASIC.TXT Page 2 Review SUBDEMO.BAS. You will see a method which allows the Basic program to determine if it is running compiled or interpreted. (The interpreter uses 3 bytes for the string descriptor; the compiler uses 4 bytes. Hence, the difference in the offsets of successive elements of a string array is either 3 or 4.) If SUBDEMO.BAS is running compiled, it will jump to step 11. For the steps 6-10, assume you are still developing the code under the interpreter. 6) Bring up the Basic interpreter. Load and run a program with code equivalent to SUBDEMO.BAS. This will check for running under the interpreter. Since it is, it will do the following: 7) Poke a two line subroutine into an array: INT 67 RET We call this subroutine SUBINIT. (Note that ASMBASIC services interrupt 67.) 8) Call SUBINIT with an argument of 1 to learn the offset of subroutine 1 (the first subroutine in the subroutine table). Call SUBINIT with a 2 to learn the offset of subroutine 2, etc. I% = 1 'We want the offset to the first routine CALL SUBINIT(I%) 'SUBINIT will place the offset into I% QPRINT = I% 'Copy the offset into variable QPRINT I% = 2 'We want the offset to the second routine CALL SUBINIT(I%) 'SUBINIT will place the offset into I% SUB2 = I% 'Copy the offset into variable QPRINT 9) After processing all the names in the subroutine table, call SUBINIT with an argument of 0 to learn the code segment where ASMBASIC has stored your subroutines. CODESEG% = 0 'To get the Code Segment, use 0 as input. CALL SUBINIT(CODESEG%) 'SUBINIT will return code segment. 10) Use the Basic DEF SEG statement to make your assembly language routines callable. DEF SEG = CODESEG% 11) Call the subroutines wherever and whenever you want: CALL QPRINT ( FLAG% , MYSTRING$ ) CALL SUB2( ARG1 , ARG2% , ARG3$ ) Since steps 6-10 are skipped during execution of the compiled version, the above CALL statements are executed as we desire. Under the interpreter, QPRINT and SUB2 are treated as VARIABLES. File: ASMBASIC.TXT Page 3 Some General Comments An obvious benefit to using ASMBASIC over storing assembly subroutines in arrays is that the assembly code does not reduce the amount of memory available for variables and string storage (assuming sufficient physical memory in the machine). Assembly language programmers will appreciate the ease with which assembly routines can be debugged. Link your new subroutine into ASMBASIC. Invoke ASMBASIC to make your routine resident. Bring up the Basic interpreter under DOS Debug: DEBUG BASICA.COM You can find the subroutine table by looking down the interrupt vector for INT 67h (used to invoke SUBINIT). -D 00:19C L4 0000:019C 2C 00 10 06 The subroutine table is 2 bytes after the address in the interrupt vector. In this example: 0610:002C + 2 = 0610:002E Display the subroutine table. -D 0610:002E L10 0610:002E 19 01 0610:0030 30 01 B0 02 F0 02 D0 04-30 03 70 02 F0 01 The eight subroutines in this example can be found at 0610:0119 QPRINT 1 0610:0130 SCRLDN 2 0610:02B0 SCRLUP 3 0610:02F0 XREP 4 0610:04D0 YREP 5 0610:0330 CLREOL 6 0610:0270 CLREOS 7 0610:01F0 ZPRINT 8 Start the interpreter and set breakpoints at the start of the subroutines you wish to debug: -G 610:119 ASMBASIC will not make itself resident more than once after the system is booted. If you are modifying and assembling a new subroutine (and relinking it into ASMBASIC) then you should comment out the: JMP SHORT ALREADY_RESIDENT instruction in ASMBASIC so that each execution of ASMBASIC will make your latest copy of the subroutine available for debugging. File: ASMBASIC.TXT Page 4 A subtle benefit of ASMBASIC is easy addressability for local data segments. In contrast, assembly routines POKE'd or BLOAD'ed into memory do not have relocation services performed for them. For example, the following subroutine will work with ASMBASIC and will fail if attempted with POKE. LOCLSEG SEGMENT my local data LOCLSEG ENDS CODE SEGMENT PARA PUBLIC 'CODE' PUBLIC MYSUB ASSUME CS:CODE,ES:LOCLSEG MYSUB PROC FAR MOV AX,SEG LOCLSEG MOV ES,AX rest of subroutine MYSUB ENDP CODE ENDS END The standard way of allowing routines called by BASIC to have their own data areas has some undesirable aspects. First, the code must be linked /HIGH. Then, it is loaded by the Debugger (who performs the relocation needed), BSAVE'ed by the interpreter, then BLOAD'ed into the exact same location by the interpreter when needed. There is an incomplete description of this latter method in the back of the BASIC manual. Note that the location occupied by such a BLOAD'ed routine is the same memory location which will hold BASRUN.EXE if the BASIC code is ever compiled and executed. For a more reasonable approach, try ASMBASIC.