Basic Parameter Passing to MASM
ID Number:  Q10293

3.x 5.x
MS-DOS


Summary:
   The following information concerns how parameters are passed
between compiled BASIC and Assembler.

More Information:
   With compiled BASIC, you have no choice as to how variables are
passed; they are all passed by address. The following rules need to be
in place to make this work properly: 

   1. The called routine must preserve segment registers DS, ES, SS,
and the base pointer (BP). If interrupts are disabled in the routine,
they must be enabled before exiting. The stack must be cleaned up on
exit. 
   2. The called program must know the number and length of parameters
passed. The following routine shows an easy way to reference
parameters: 

     PUSH BP
     MOV  BP,SP
     ADD  BP, (2*number of parameters)+4

so that

   parameter 0 is at BP
   parameter 1 is at BP-2
   parameter n is at BP-2*n
   (number of parameters = n+1)

   3. Variables may be allocated either in the code segment or on the
stack. Be careful not to modify the return segment and offset stored
on the stack. 
   4. The BASIC language pushes all parameters onto the stack in the
same order in which they are declared; then, it does a FAR call. 
   5. The called routine must clean up the stack and do a FAR return.
A preferred way to do this is to perform a RETURN <n> statement where
<n> is 2*number of parameters passed. This procedure is done to adjust
the stack to the start of the calling sequence. 
   6. Data areas needed by the routine must be allocated either in the
CODE segment of the user routine or on the stack. It is not possible
to declare a separate data area in the assembler routine. 

   There are two ways to pass control to an assembler program from
BASIC. One way is to use the USR statement, the other is to use the
CALL statement. Each has advantages and disadvantages but, basically,
the USR statement can only pass one parameter (to a special area in
BASIC called the Floating Point Accumulator area); it returns just one
argument. The USR statement is used mainly to be compatible with many
different versions of BASIC and the interpretive BASIC. 
   However, the CALL statement can pass and return many parameters
quite easily because it places the address of the parameters on the
stack. The CALL statement is the preferred method of parameter passing
between program and routines. 
   Thus, the BASIC program (passing two integers) and subroutine EXP
works as follows: 

100 DEF SEG=&H8000 110 FOO=&H7FA 120 CALL FOO (A,B)

   Line 100 sets the segment to 8000H. The call to FOO will execute a
subroutine at location 8000:7FAh. 

PUBLIC    EXP

CSEG      SEGMENT
ASSUME    CS:CSEG

EXP       PROC FAR
          PUSH BP        ;SET UP POINTER TO ARGUMENTS
          MOV  BP,SP     ;MAKE BP ADDRESSABLE TO STACK
          ADD  BP,(2*3+4);REFERENCE ARGUMENTS TO BP
          MOV  BX,[BP-2] ;GET ADDRESS OF A FROM STACK
          MOV  AX,[BX]   ;GET 'A' PARAMETER
          MOV  BP,[BP-4] ;GET ADDRESS OF B FROM STACK
          MOV  DX,[BX]   ;GET 'B' PARAMETER
          POP  BP        ;RESTORE POINTER
          RET  4         ;RESTORE STACK
EXP       ENDP
CSEG      ENDS
          END

   Memory looks like the following:

BP -------> BP (2 bytes) ;original BP value placed on Stack BP + 2
---> Offset(2 bytes);offset of return address BP + 4 ---> Segment (2
bytes) ;segment of return address BP + 6 ---> Par 2 (2 bytes);address
of second parameter BP + 8 ---> Par 1 (2 bytes);address of first
parameter 

   There is no need to declare EXP as an external routine because
compiled BASIC assumes that all CALLs of this form are to external
routines. The assembly routine assumes that the parameters are
two-byte integers, whereas BASIC assumes the parameters to be 4-byte
real numbers. This is why you need to specifically declare variables
as integers. 
   For passing information as strings, compiled BASIC and interpretive
BASIC differ. 
   With compiled BASIC, the first two bytes are the string length and
the last two bytes are the string's starting address. When a string
parameter is passed on the stack to an assembly routine, it is the
address of the string descriptor that actually is passed, not the
address of the string. 
   To pass a string variable to an assembly routine from compiled
BASIC, pass the name of the string variable. Thus, to call a routine
that passes text, use CALL NN(A%,B%,TEXT%), where X% and Y% are
integer variables and TEXT% is a string variable. 
   To return a value to a compiled BASIC program, just use the CALL
statement in which the parameter whose value you need to change is
passed to the assembly routine. You then can change this value within
your assembler routine, since you know where the parameter's address
is stored in memory on the stack. 
