by Richard Russell, November 2008
You can't actually call BASIC from assembler code, but you can achieve an equivalent capability quite easily. What it involves is returning from the assembler code to BASIC, executing a BASIC procedure, and then re-entering the assembler code at the right place.
The first step in achieving this is to modify the way your assembler code is executed. Instead of a simple CALL statement:
Instead use the following code:
B% = USR(code_start) WHILE B% PROC(B%) B% = USR(P%) ENDWHILE
If you prefer to call your assembler code with SYS then use code similar to the following:
SYS code_start, parameters TO B% WHILE B% PROC(B%) B% = USR(P%) ENDWHILE
It is also necessary to adapt the assembler code entry point itself, as follows:
DIM mystack% 255, myesp% 3, gap% 2047, mycode% 1000, L% -1 FOR pass% = 8 TO 10 STEP 2 P% = myesp% [OPT pass% .saved_esp dd mystack%+256 ] P% = mycode% [OPT pass% .code_start xchg esp,[saved_esp] ; rest of assembler code continues here
Here a dual stack arrangement is established, where mystack% is a 256-byte memory area containing the second stack. Needless to say, you should increase (or decrease) the amount of memory allocated for the assembler code (here shown as 1000 bytes) as required.
When you want to 'call' a BASIC procedure from assembler code, do so as follows:
mov eax,^PROCsomething call CallBASIC
Note that only procedures without parameters can straightforwardly be called this way. If you need to transfer data from the assembler code into the procedure(s), the easiest way to do so is via global variables. You can call as many procedures as you like, and they can be called from anywhere within the assembler code (even within a subroutine, or if values have been pushed onto the stack).
Since procedure pointers are used, the usual restriction applies that at least one PROC must have been called conventionally before the code is assembled (if the assembly routine itself is in a procedure, this is sufficient).
When you finally want to exit from the assembler code, instead of a simple ret use this code:
mov eax,0 xchg esp,[saved_esp] ret
If you need to call one of the 'OS' routines, such as oswrch, you must do so as follows:
xchg esp,[saved_esp] call "oswrch" xchg esp,[saved_esp]
Finally, here is the CallBASIC subroutine:
.CallBASIC xchg esp,[saved_esp] mov dword [^P%],next ret .next xchg esp,[saved_esp] ret