This appendix is written primarily for users experienced in assembly language programming.
GW-BASIC lets you interface with assembly language subroutines by using the USR function and the CALL statement.
The USR function allows assembly language subroutines to be called in the same way GW-BASIC intrinsic functions are called. However, the CALL statement is recommended for interfacing machine language programs with GW-BASIC. The CALL statement is compatible with more languages than the USR function call, produces more readable source code, and can pass multiple arguments.
Memory space must be set aside for an assembly language (or machine code) subroutine before it can be loaded. There are three recommended ways to set aside space for assembly language routines:
If, when an assembly language subroutine is called, more stack space is needed, GW-BASIC stack space can be saved, and a new stack set up for use by the assembly language subroutine. The GW-BASIC stack space must be restored, however, before returning from the subroutine.
CALL variablename[(arguments)]
variablename contains the offset in the current segment of the subroutine being called.
arguments are the variables or constants, separated by commas, that are to be passed to the routine.
For each parameter in arguments, the 2-byte offset of the parameter's location within the data segment (DS) is pushed onto the stack.
The GW-BASIC return address code segment (CS), and offset (IP) are pushed onto the stack.
A long call to the segment address given in the last DEF SEG statement and the offset given in variablename transfers control to the user's routine.
The stack segment (SS), data segment (DS), extra segment (ES), and the stack pointer (SP) must be preserved.
Figure D.1 shows the state of the stack at the time of the CALL statement:
Figure 1
The user's routine now has control. Parameters may be referenced by moving the stack pointer (SP) to the base pointer (BP) and adding a positive offset to BP.
Upon entry, the segment registers DS, ES, and SS all point to the address of the segment that contains the GW-BASIC interpreter code. The code segment register CS contains the latest value supplied by DEF SEG. If no DEF SEG has been specified, it then points to the same address as DS, ES, and SS (the default DEF SEG).
Figure D.2 shows the condition of the stack during execution of the called subroutine:
Figure 2
The following seven rules must be observed when coding a subroutine:
Note
The called routine must not change the contents of any of the three bytes of the string descriptor.
If the argument is a string literal in the program, the string descriptor points to program text. Be careful not to alter or destroy your program this way. To avoid unpredictable results, add +"" to the string literal in the program. For example, the following line forces the string literal to be copied into string space allocated outside of program memory space:
20 A$="BASIC"+""
The string can then be modified without affecting the program.
Examples:
100 DEF SEG=&H2000 110 ACC=&H7FA 120 CALL ACC(A,B$,C)
. . .
Line 100 sets the segment to 2000 hex. The value of variable ACC is added into the address as the low word after the DEF SEG value is left-shifted four bits (this is a function of the microprocessor, not of GW-BASIC). Here, ACC is set to &H7FA, so that the call to ACC executes the subroutine at location 2000:7FA hex.
Upon entry, only 16 bytes (eight words) remain available within the allocated stack space. If the called program requires additional stack space, then the user program must reset the stack pointer to a new allocated space. Be sure to restore the stack pointer adjusted to the start of the calling sequence on return to GW-BASIC.
The following assembly language sequence demonstrates access of the parameters passed and storage of a return result in the variable C.
Note
The called program must know the variable type for numeric parameters passed. In these examples, the following instruction copies only two bytes:
MOVSW
This is adequate if variables A and C are integer. It would be necessary to copy four bytes if they were single precision, or copy eight bytes if they were double precision.
MOV BP,SP | Gets the current stack position in BP |
MOV BX,8[BP] | Gets the address of B$ description |
MOV CL,[BX] | Gets the length of B$ in CL |
MOV DX,1[BX] | Gets the address of B$ string descriptor in DX |
MOV SI,10[BP] | Gets the address of A in SI |
MOV DI,6[BP] | Gets the pointer to C in DI |
MOVSW | Stores variable A in 'C' |
RET 6 | Restores stack; returns |
Although the CALL statement is the recommended way of calling assembly language subroutines, the USR function call is still available for compatibility with previously-written programs.
USR[n](argument)
n is a number from 0 to 9 which specifies the USR routine being called (see DEF USR statement). If n is omitted, USR0 is assumed.
argument is any numeric or string expression.
In GW-BASIC a DEF SEG statement should be executed prior to a USR function call to ensure that the code segment points to the subroutine being called. The segment address given in the DEF SEG statement determines the starting segment of the subroutine.
For each USR function call, a corresponding DEF USR statement must have been executed to define the USR function call offset. This offset and the currently active DEF SEG address determine the starting address of the subroutine.
When the USR function call is made, register AL contains the number type flag (NTF), which specifies the type of argument given. The NTF value may be one of the following:
NTF Value | Specifies |
2 | a two-byte integer (two's complement format) |
3 | a string |
4 | a single-precision floating point number |
8 | a double-precision floating point number |
If the argument of a USR function call is a number (AL<>73), the value of the argument is placed in the floating-point accumulator (FAC). The FAC is 8 bytes long and is in the GW-BASIC data segment. Register BX will point at the fifth byte of the FAC. Figure D.3 shows the representation of all the GW-BASIC number types in the FAC:
Figure 3
If the argument is a single-precision floating-point number:
If the argument is an integer:
If the argument is a double-precision floating-point number:
If the argument is a string (indicated by the value 3 stored in the AL register) the (DX) register pair points to three bytes called the string descriptor. Byte 0 of the string descriptor contains the length of the string (0 to 255). Bytes 1 and 2, respectively, are the lower- and upper-eight bits of the string starting address in the GW-BASIC data segment.
If the argument is a string literal in the program, the string descriptor points to program text. Be careful not to alter or destroy programs this way (see the preceding CALL statement).
Usually, the value returned by a USR function call is the same type (integer, string, single precision, or double precision) as the argument that was passed to it. The registers that must be preserved are the same as in the CALL statement.
A far return is required to exit the USR subroutine. The returned value must be stored in the FAC.
This section contains two sample GW-BASIC programs that
The code segment and offset to the first routine is stored in interrupt vector at 0:100H.
Example 1 calls an assembly language subroutine:
10 DEF SEG=0 100 CS=PEEK(&H102)+PEEK(&H103)*256 200 OFFSET=PEEK(&H100)+PEEK(&H101)*256 250 DEF SEG 300 C1%=2:C2%=3:C3%=0 400 TWOSUM=OFFSET 500 DEF SEG=CS 600 CALL TWOSUM(C1%,C2%,C3%) 700 PRINT C3% 800 END
The assembly language subroutine called in the above program must be assembled, linked, and converted to a .COM file. The program, when executed prior to the running of the GW-BASIC program, will remain in memory until the system power is turned off, or the system is rebooted.
org 100H 0100 double segment assume cs:double 0100 EB 17 90 start: jmp start1 0103 usrprg proc far 0103 55 push bp 0104 8B EC mov bp,sp 0106 8B 76 08 mov si,[bp]+8 ;get address of parameter b 0109 8B 04 mov ax,[si] ;get value of b 010B 8B 76 0A mov si,[bp]+10 ;get address of parameter a 010E 03 04 add ax,[si] ;add value of a to value of b 0110 8B 7E 06 mov di,[bp]+6 get address of parameter c 0113 89 05 mov di,ax ;store sum in parameter c 0115 5D pop bp 0116 ca 00 06 ret 6 0119 usrprg endp ;Program to put procedure in ;memory and remain resident. The ;offset and segment are stored ;in location 100-103H. 0119 start1: 0119 B8 00 00 mov ax,0 011C 8E D8 mov ds,ax ;data segment to 0000H 011E BB 01 00 mov bx,0100H ;pointer to int vector 100H 0121 83 7F 02 0 cmp word ptr [bx],0 0125 75 16 jne quit ;program already run, exit 0127 83 3F 00 cmp word ptr2 [bx],0 012A 75 11 jne quit ;program already run exit 012C B8 01 03 R mov ax,offset usrprg 012F 89 07 mov [bx],ax ;program offset 0131 8C c8 mov ax,cs 0133 89 47 02 mov [bx+2],ax ;data segment 0136 0E push cs 0137 1F pop ds 0138 BA 0141 R mov dx,offset veryend 013B CD 27 int 27h 013D quit: 013D CD 20 int 20h 013F veryend: 013F double ends end start
Example 2 places the assembly language subroutine in the specified area:
10 I=0:JC=0 100 DIM A%(23) 150 MEM%=VARPTR(A%(1)) 200 FOR I=1 TO 23 300 READ JC 400 POKE MEM%,JC 450 MEM%=MEM%+1 500 NEXT 600 C1%=2:C2%=3:C3%=0 700 TWOSUM=VARPTR(A%(1)) 800 CALL TWOSUM(C1%,C2%,C3%) 900 PRINT C3% 950 END 1000 DATA &H55,&H8b,&Hec &H8b,&H76,&H08,&H8b,&H04,&H8b,&H76 1100 DATA &H0a,&H03,&H04,&H8b,&H7e,&H06,&H89,&H05,&H5d 1200 DATA &Hca,&H06,&H00