[November 1989]

How to Pass Parameters between BASIC and Assembly Language    
----------------------------------------------------------

This document explains how Microsoft BASIC compiled programs can pass
parameters to and from Microsoft Macro Assembler (MASM) programs.
This document assumes that you have a basic understanding of BASIC and
assembly language.

Microsoft BASIC supports calls to routines written in Microsoft Macro
Assembler, FORTRAN, Pascal, and C.  This document describes the
necessary syntax for calling Microsoft assembly-language procedures,
and contains a series of examples demonstrating the interlanguage
calling capabilities between BASIC and assembly language.  The sample
programs apply to the following Microsoft Products:

1. Microsoft QuickBASIC Versions 4.00, 4.00b, and 4.50 for MS-DOS

2. Microsoft BASIC Compiler Versions 6.00 and 6.00b for MS OS/2 and
   MS-DOS

3. Microsoft Macro Assembler Versions 5.00 and 5.10 for MS OS/2 and
   MS-DOS

4. Microsoft QuickAssembler Version 2.01 (which is integrated as part
   of Microsoft QuickC Compiler with QuickAssembler Version 2.01) for
   MS-DOS

The following table specifies which versions of Microsoft BASIC can be
linked with specific versions of Microsoft Macro Assembler or
QuickAssembler:

                    BASIC
      QuickBASIC    Compiler  <->  MASM      QuickAssembler
      ----------    --------  ---  ----      --------------
      4.00          --        <->  5.00 or   --
      --            6.00      <->  5.10 or   --
      4.00b    or   6.00b     <->  5.10 or   2.01
      4.50          --        <->  5.10 or   2.01

For more information about interlanguage calling, refer to the
"Microsoft Mixed Language Programming Guide."  This guide is available
with C 5.00 and 5.10 and MASM 5.00 and 5.10.

     Before you read or use this information, it is important to
     read this notice of disclaimer.
      
     The documents included in this package are meant for
     informational purposes only.  Microsoft makes no warranties,
     either expressed or implied, as to this information's
     suitability for specific purposes or its correctness, accuracy,
     or reliability.  The entire risk as to the results and
     performance of the software is assumed by you.
      
     Neither the authors nor Microsoft nor anyone else who has been
     involved in the creation, production, or delivery of this
     information/software shall be liable for any direct, indirect,
     consequential, or incidental damages (including damages for
     loss of business profits, business interruption, loss of
     business information, and the like) arising out of the use or
     inability to use such information/software even if Microsoft
     has been advised of the possibility of such damages.  Because
     some states do not allow the exclusion or limitation of
     liability for consequential or incidental damages, the above
     limitation may not apply to you.
      
     
1. MAKING MIXED-LANGUAGE CALLS


   Mixed-language programming always involves a call; specifically, it
   involves a function or subprogram call.  For example, a BASIC main
   module may need to execute a specific task that you would like to
   program separately.  Instead of calling a BASIC subprogram,
   however, you can call an assembly-language procedure.

   Mixed-language calls necessarily involve multiple modules.  Instead
   of compiling all of your source modules with the same compiler, you
   use different compilers.  In the example mentioned above, you would
   compile the main-module source file with the BASIC compiler,
   assemble another source file (written in assembly language) with
   the assembler, and then link together the two object files.

   There are two types of routines that can be called.  Their
   principle difference is that some return values, and others do not.
   (Note:  In this document, "routine" refers to any function or
   subprogram procedure that can be called from another module.)

     Note:  BASIC DEF FN functions and GOSUB subroutines cannot
     be called from another language.

     
      BASIC has a much more complex environment and
      initialization procedure than assembly language.  Because
      of this, BASIC must be the initial environment that the
      program starts up in, and from there, assembly-language
      routines can be called (which can in turn call BASIC
      routines).  This means that a program cannot start up in
      assembly language and then call BASIC routines.
     

2. THE BASIC INTERFACE TO ASSEMBLY-LANGUAGE


   The BASIC DECLARE statement provides a flexible and convenient
   interface to assembly language.  When you call a routine, the
   DECLARE statement syntax is as follows:

      DECLARE FUNCTION name [ALIAS "aliasname"][(parameter-list)]

   The name field is the name of the function or subprogram that you
   want to call, as it appears in the BASIC source file.  Here are the
   recommended steps for using the DECLARE statement when calling
   assembly language:

   a. For each distinct assembly-language routine you plan to call,
      put a DECLARE statement in your BASIC source file before the
      routine is called.

   b. If you are calling a MASM routine with a name longer than 31
      characters, use the ALIAS feature.  The use of ALIAS is
      explained below.

   c. Use the parameter list to determine how each parameter is to be
      passed.  The use of the parameter list is explained below.

   d. Once the routine is properly declared, call it just as you would
      a BASIC subprogram or function.

   Naming Convention Requirements
   ------------------------------

   The term "naming convention" refers to the way that a compiler
   alters the name of the routine before placing it into an object
   file.

   It is important that you adopt a compatible naming convention when
   you issue a mixed-language call.  If the name of the called routine
   is stored differently in each object file, then the linker will not
   be able to find a match.  Instead, it will report an unresolved
   external.

   Microsoft compilers place machine code into object files, but they
   also place the names of all routines and common blocks that need to
   be accessed publicly into object files. (Note: BASIC variables are
   never public symbols.)  That way, the linker can compare the name
   of a routine called in one module to the name of a routine defined
   in another module, and recognize a match.

   BASIC and MASM use the same naming conventions.  They both
   translate each letter of public names to uppercase.  BASIC drops
   the type declaration character (%, &, !, #, $).  BASIC recognizes
   the first 40 characters of a routine name, while MASM recognizes
   the first 31 characters of a name.

   Calling Convention Requirements
   -------------------------------

   The term "calling convention" refers to the way that a language
   implements a call.  The choice of calling convention affects the
   actual machine instructions that a compiler generates to execute
   (and return from) a function, procedure, or subroutine call.

   The use of a calling convention affects programming in two ways:

   a. The calling routine uses a calling convention to determine in
      what order to pass arguments (parameters) to another routine.
      The convention can usually be specified in a mixed-language
      interface.

   b. The called routine uses a calling convention to determine in
      what order to receive the parameters that were passed to it.  In
      most languages, this convention can be specified in the
      routine's heading.  BASIC, however, always uses its own
      convention to receive parameters.

   BASIC's calling convention pushes parameters onto the stack in the
   order in which they appear in the source code.  For example: the
   BASIC statement CALL Calc(A, B) pushes argument A onto the stack
   before it pushes B.  This convention also specifies that the stack
   is restored by the called routine, just before returning control to
   the caller.  (The stack is restored by removing parameters.)

   Using ALIAS
   -----------

   The use of ALIAS may be necessary because assembly language places
   the first 31 characters of a name into an object file, whereas
   BASIC places up to 40 characters of a name into an object file.

     Note:  You do not need the ALIAS feature to remove type
     declaration characters (%, &, !, #, $).  BASIC automatically
     removes these characters when it generates object code.
     Thus, Fact% in BASIC matches FACT in assembly language.

   The ALIAS keyword directs BASIC to place aliasname into the object
   file, instead of name.  The BASIC source file still contains calls
   to name.  However, these calls are interpreted as if they were
   actually calls to aliasname.  This is used when a BASIC name is
   longer then 31 characters and must be called from assembly
   language.

   For example:

      DECLARE FUNCTION   QuadraticPolynomialFunctionLeastSquares%
                         ALIAS "QUADRATI" (a, b, c)

   In the example above, QUADRATI, the aliasname, contains the first
   eight characters of the name
   QuadraticPolynomialFunctionLeastSquares%.  This causes BASIC to
   place QUADRATI into the object file, thereby mimicking MASM's
   behavior.

   Using the Parameter List
   ------------------------

   The parameter-list syntax is displayed below, followed by
   explanations of each field:

      [BYVAL | SEG] variable [AS type]...,

     Note: You can use BYVAL or SEG, but not both.

   Use the BYVAL keyword to declare a value parameter.  In each
   subsequent call, the corresponding argument will be passed by
   value.

     Note:  BASIC provides two ways of "passing by value."  The
     usual method of passing by value is to use an extra set of
     parentheses, as in the following:

        CALL HOLM((A))

     This method actually creates a temporary value, whose
     address is passed.  In contrast, BYVAL provides a true
     method of passing by value, because the value itself is
     passed, not an address.  Only by using BYVAL will a BASIC
     program be compatible with an assembly-language routine that
     expects a value parameter.

   Use the SEG keyword to declare a far reference parameter.  In each
   subsequent call, the far (segmented) address of the corresponding
   argument will be passed.

   You can choose any legal name for variable, but only the type
   associated with the name has any significance to BASIC.  As with
   other variables, the type can be indicated with a type declaration
   character (%, &, !, #, $) or the implicit declaration.

   You can use the "AS type" clause to override the type declaration
   of variable.  The type field can be INTEGER, LONG, SINGLE, DOUBLE,
   STRING, a user-defined type, or ANY, which directs BASIC to permit
   any type of data to be passed as the argument.

   For example:

      DECLARE FUNCTION Calc2! (BYVAL a%, BYVAL b%, BYVAL c!)

   In the example above, Calc2! is declared as an assembly-language
   routine that takes three arguments: the first two are integers
   passed by value, and the last is a single-precision real number
   passed by value.

   Alternative BASIC Interfaces
   ----------------------------

   You can specify parameter-passing methods without using a DECLARE
   statement or by using a DECLARE statement and omitting the
   parameter list.

   a. You can make the call with the CALLS statement.  The CALLS
      statement causes each parameter to be passed by far reference.

   b. You can use the BYVAL and SEG keywords in the actual parameter
      list when you make the call, as follows:

         CALL Fun2(BYVAL Term1, BYVAL Term2, SEG Sum)

   In the example above, BYVAL and SEG have the same meaning that they
   have in a BASIC DECLARE statement.  When you use BYVAL and SEG this
   way, however, you need to be careful because neither the type nor
   the number of parameters will be checked as they would be in a
   DECLARE statement.

3. SETTING UP THE ASSEMBLY-LANGUAGE PROCEDURE


   The linker cannot combine the assembly-language procedure with the
   calling program unless compatible segments are used and the
   procedure itself is declared properly.  The following points may be
   helpful:

   a. Use the .MODEL directive at the beginning of the source file, if
      you have Version 5.00 of the Macro Assembler; this directive
      automatically causes the appropriate kind of returns to be
      generated (NEAR for small or compact model, FAR otherwise).
      Modules called from BASIC should be declared as .MODEL MEDIUM.
      If you have a version of the assembler earlier than 5.00,
      declare the procedure FAR.

   b. If you have Version 5.00 or later of the Microsoft Macro
      Assembler, use the simplified segment directives .CODE to
      declare the code segment and .DATA to declare the data segment.
      (Having a code segment is sufficient if you do not have data
      declarations.)  If you are using an earlier version of the
      assembler, the SEGMENT, GROUP, and ASSUME directives must be
      used.

   c. The procedure label must be declared public with the PUBLIC
      directive.  This declaration makes the procedure available to be
      called by other modules.  Also, any data you want to make public
      to other modules must be declared as PUBLIC.

   d. Global data or procedures accessed by the routine must be
      declared EXTRN.  The safest way to use EXTRN is to place the
      directive outside any segment definition (however, near data
      should generally go inside the data segment).

   Preserving Registers
   --------------------

   There are several registers that need to be preserved in a mixed-
   language program.  These registers are as follows:

      CX, BX
      BP, SI, DI, SP
      CS, DS, SS, ES

   The direction flag should also be preserved (for safety, all the
   flags should be preserved).

   On the 80286 and 80386, all extended registers should also be
   preserved.

   Entering the Assembly-Language Procedure
   ----------------------------------------

   The following two instructions begin the procedure:

      push     bp
      mov      bp, sp

   This sequence establishes BP as the "framepointer."  The
   framepointer is used to access parameters and local data, which are
   located on the stack.  SP cannot be used for this purpose because
   it is not an index or base register.  Also, the value of SP may
   change as more data is pushed onto the stack.  However, the value
   of the base register BP will remain constant throughout the
   procedure, so that each parameter can be addressed as a fixed
   displacement off of BP.

   The instruction sequence above first saves the value of BP, since
   it will be needed by the calling procedure as soon as the current
   procedure terminates.  Then BP is loaded with the value of SP to
   capture the value of the pointer at the time of entry to the
   procedure.

   Allocating Local Data (Optional)
   --------------------------------

   An assembly-language procedure can use the same technique for
   implementing local data that is used by high-level languages.  To
   set up local data space, decrease the contents of SP in the third
   instruction of the procedure.  (To ensure correct execution, you
   should always increase or decrease SP by an even amount.)
   Decreasing SP reserves space on the stack for the local data.  The
   space must be restored at the end of the procedure, as shown below:

      push     bp
      mov      bp, sp
      sub      sp, space

   In the text above, space is the total size in bytes of the local
   data.  Local variables are then accessed as fixed, negative
   displacements off of BP.

   For example:

      push     bp
      mov      bp, sp
      sub      sp, 4
         .
         .
         .
      mov      WORD PTR [bp-2], 0
      mov      WORD PTR [bp-4], 0

   The example above uses two local variables, each of which is 2
   bytes in size.  SP is decreased by 4, since there are 4 bytes of
   local data.  Later, each of the variables is initialized to 0
   (zero).  These variables are never formally declared with any
   assembler directive; the programmer must keep track of them
   manually.

   Local variables are also called dynamic, stack, or automatic
   variables.

   Exiting the Procedure
   ---------------------

   Several steps may be involved in terminating the procedure:

   a. If any of the registers SS, DS, SI, etc. have been saved, these
      must be popped off the stack in the reverse order that they were
      saved.

   b. If local data space was allocated at the beginning of the
      procedure, SP must be restored with the instruction MOV SP, BP.

   c. Restore BP with POP BP.  This step is always necessary.

   d. Finally, return to the calling program with the RET n
      instruction (where n is the number of bytes to pop off the
      stack) to adjust the stack with respect to the parameters that
      were pushed by the caller.

   Assembly-Language Calls to BASIC
   --------------------------------

   No BASIC routine can be executed unless the main program is in
   BASIC, because a BASIC routine requires the environment to be
   initialized in a way that is unique to BASIC.  MASM will not
   perform this special initialization.

   However, a program can start up in BASIC, call an assembly-language
   function that does most of the work of the program, and then call
   BASIC subprograms and functions as needed.

   The following rules are recommended when you call BASIC from
   assembly language:

   a. Start up in a BASIC main module.  You must use the DECLARE
      statement to provide an interface to the assembly-language
      module. (Note: This is required.)

   b. In the assembly-language module, declare the BASIC routine as
      EXTRN.

   c. Make sure that all data is passed as a near pointer.  BASIC can
      pass data in a variety of ways, but is unable to receive data in
      any form other than near reference.

        Note: With near pointers, the program assumes that the
        data is in the default data segment.  If you want to pass
        data that is not in the default data segment, then first
        copy the data to a variable that is in the default data
        segment.

   The Microsoft Segment Model
   ---------------------------

   If you use the simplified segment directives by themselves, you do
   not need to know the names assigned for each segment.  However,
   versions of the Macro Assembler earlier than 5.00 do not support
   these directives.  With earlier versions of the assembler, you
   should use the SEGMENT, GROUP, ASSUME, and ENDS directives
   equivalent to the simplified segment directives.

   The following table shows the default segment names created by the
   .MODEL MEDIUM directive used with BASIC.  Use of these segments
   ensures compatibility with Microsoft languages and will help you
   access public symbols.  This table is followed by a list of three
   steps, illustrating how to make the actual declarations, and a
   sample program.

      Directive   Name      Align     Combine   Class     Group
      ---------   ----      -----     -------   -----     -----
      .CODE       name_TEXT WORD      PUBLIC    'CODE'
      .DATA       _DATA     WORD      PUBLIC    'DATA'    DGROUP
      .CONST      CONST     WORD      PUBLIC    'CONST'   DGROUP
      .DATA?      _BSS      WORD      PUBLIC    'BSS'     DGROUP
      .STACK      STACK     PARA      STACK     'STACK'   DGROUP

   The directives in the table refer to the following kinds of
   segments:

   Directive       Description of Segment
   ---------       ----------------------
   .CODE           The segment containing all the code for the module.
   .DATA           Initialized data.
   .DATA?          Uninitialized data.  Microsoft compilers store
                   uninitialized data separately because it can be
                   more efficiently stored than initialized data.
                   (Note: BASIC does not use uninitialized data.)
   .FARDATA and
   .FARDATA?       Data placed here will not be combined with the
                   corresponding segments in other modules.  The
                   segment of data placed here can always be
                   determined, however, with the assembler SEG
                   operator.
   .CONST          Constant data.  Microsoft compilers use this
                   segment for such items as string and floating-point
                   constants.
   .STACK          Stack.  Normally, this segment is declared in the
                   main module for you and should not be redeclared.

   The following steps describe how to use this table to create
   directives:

   a. Refer to the table to look up the segment name, align type,
      combine type, and class for your code and data segments.  Use
      all of these attributes when you define a segment.  For example,
      the code segment is declared as follows:

         _TEXT     SEGMENT   WORD PUBLIC 'CODE'

      The name _TEXT and all the attributes are taken from the table.

   b. If you have segments in DGROUP, put them into DGROUP with the
      GROUP directive, as in the following:

         GROUP     DGROUP    _DATA     _BSS

   c. Use ASSUME and ENDS as you would normally.  Upon entry, DS and
      SS will both point to DGROUP.

   The following example shows an assembly-language program without
   the simplified segment directives from Version 5.00 of the
   Microsoft Macro Assembler:

      _TEXT    SEGMENT WORD PUBLIC 'CODE'
               ASSUME    cs:_TEXT
               PUBLIC _Power2
      _Power2  PROC
               push bp
               mov bp, sp
      
               mov ax, [bp+6]
               mov cx, [bp+8]
               shl ax, cl
      
               pop bp
               ret 4
      _Power2  ENDP
      _TEXT    ENDS
               END

4. COMPILING AND LINKING


   After you have written your source files and resolved the issues
   raised in the above sections, you are ready to compile individual
   modules and then link them together.

   Before linking, each program module must be compiled or assembled
   with the appropriate compiler or assembler.

5. ACCESSING PARAMETERS


   Parameter-Passing Requirements
   ------------------------------

   Microsoft compilers support three methods for passing a parameter:

   Method          Description
   ------          -----------
   Near reference  Passes a variable's near (offset) address.  This
                   method gives the called routine direct access to
                   the variable itself.  Any change the routine makes
                   to the parameter will be reflected in the calling
                   routine.
                   
   Far reference   Passes a variable's far (segmented) address.  This
                   method is similar to passing by near reference,
                   except that a longer address is passed.
                   
   By value        Passes only the variable's value, not address.
                   With this method, the called routine knows the
                   value of the parameter, but has no access to the
                   original variable.  Changes to the value parameter
                   have no effect on the value of the parameter in the
                   calling routine, once the routine terminates.

   Because there are different parameter-passing methods, you must be
   aware of a couple of points:

   a. You need to make sure that the called routine and the calling
      routine use the same method for passing each parameter
      (argument).  In most cases, you will need to check the
      parameter-passing defaults used by each language, and possibly
      make adjustments.  Each language has keywords or language
      features that allow you to change the parameter-passing method.

   b. You may want to use a particular parameter-passing method rather
      then using the default for the language.

   BASIC Arguments
   ---------------

   The default for BASIC is to pass all arguments by near reference.
   This can be overriden by using CALLS instead of CALL.  CALLS causes
   BASIC to pass both the segment and offset.  CALLS can be used only
   to call a non-BASIC routine, because BASIC receives all parameters
   by near reference.

     Note:  Although BASIC can pass parameters to other languages
     by far reference using CALLS, BASIC routines can be called
     only from other languages when parameters are passed by near
     reference.  You cannot DECLARE or CALL a BASIC routine with
     parameters that have SEG or BYVAL attributes.  SEG and BYVAL
     are only used for parameters of non-BASIC routines.

   Basic Stack Frame
   -----------------

   The following diagram illustrates the BASIC stack frame as it
   appears upon entry to the assembly-language routine:

          +--------------------+
        A |   Arg 1 address    | <-- BP + 8
          |--------------------|
        B |   Arg 2 address    | <-- BP + 6
          |--------------------|
          |   Return address   |     BP + 4
          |      (4 bytes)     |     BP + 2
          |--------------------|
          |      Saved BP      | <-- BP
          +--------------------+
        
           Low addresses
        
   Assembly-Language Arguments
   ---------------------------

   Once you have established the procedure's framepointer, allocated
   local data space (if desired), and pushed any registers that need
   to be preserved, you can write the main body of the procedure.  To
   write instructions that can access parameters, consider the general
   picture of the stack frame after a procedure call, as illustrated
   in the following figure:

     
      
          High Addresses
      
                    +------------------+
                    |    Parameter     |
                    |------------------|
                    |    Parameter     |
                    |------------------|
                    |        .         |
                    |        .         |
                    |        .         |
      (Stack grows  |------------------|     Parameters above
       downward with|    Parameter     |     this generated
       each push or |------------------|     automatically by
       call.)       |  Return Address  | <-- the compiler
                    |------------------|
                    |     Saved BP     | <-- Framepointer (BP)
                    |------------------|     points here.
                    | Local Data Space |     These parameters
                    |------------------|     would be generated
                    |     Saved SI     |     by your assembly-
                    |------------------|     language code.
                    |     Saved DI     | <-- SP points to last
                    +------------------+     item placed on
                                             stack.
      
          Low Addresses
      
     

   The stack frame for the procedure is established by the following
   sequence of events:

   a. The calling program pushes each of the parameters on the stack,
      after which SP points to the last parameter pushed.

   b. The calling program issues a CALL instruction, which causes the
      return address (the place in the calling program to which
      control will ultimately return) to be placed on the stack.  This
      address may be either 2 bytes long (for near calls) or 4 bytes
      long (for far calls).  SP now points to this address.  [Note:
      When dealing with BASIC, the return address will always be a far
      address (4bytes)].

   c. The first instruction of the called procedure saves the old
      value of BP, with the instruction push bp.  SP now points to the
      saved copy of BP.

   d. BP is used to capture the current value of SP, with the
      instuction MOV BP, SP.  Therefore, BP now points to the old
      value of BP.

   e. Whereas BP remains constant throughout the procedure, SP may be
      decreased to provide room on the stack, for local data or saved
      registers.

   In general, the displacement (off of BP) for a parameter X is equal
   to the following:

      2   +    size of return address
          +    total size of parameters between X and BP

   For example, consider a FAR procedure (all BASIC procedures are
   FAR) that has received one parameter, a 2-byte address.  The
   displacement of the parameter would be as follows:

   Argument's displacement    = 2 + size of return address
                              = 2 + 4
                              = 6

   The argument can thus be loaded into BX with the following
   instruction:

      mov bx, [bp+6]

   Once you determine the displacement of each parameter, you may want
   to use string equates or structures so that the parameters can be
   referenced with a single identifier name in your assembly-language
   source code.  For example, the parameter above at BP+6 can be
   conveniently accessed if you put the following statement at the
   beginning of the assembly-language source file:

      Arg1     EQU  [bp+6]

   You could then refer to this parameter as Arg1 in any instruction.
   Use of this feature is optional.

   Passing BASIC Arguments by Value
   --------------------------------

   An argument is passed by value when the called routine is first
   declared with a DECLARE statement, and the BYVAL keyword is applied
   to the argument.  For example:

      DECLARE SUB AssemProc (BYVAL a AS INTEGER)

   Passing BASIC Arguments by Near Reference
   -----------------------------------------

   The BASIC default is to pass by near reference.  Use of SEG, BYVAL,
   or CALLS changes this default.

   Passing BASIC Arguments by Far Reference
   ----------------------------------------

   BASIC passes each argument in a call by far reference when CALLS is
   used to invoke a routine.  Using SEG to modify a parameter in a
   preceding DECLARE statement also causes a BASIC CALL to pass
   parameters by far reference.

     Note:  CALLS cannot be used to call a routine that is named
     in a DECLARE statement.

6. DATA TYPES


   Numerical Formats
   -----------------

   Numerical data formats are the simplest kinds of data to pass
   between assembly language and BASIC.  The following chart shows the
   equivalent data types in each language:

      BASIC           Assembly Language
      -----           -----------------
      x%, INTEGER     DW
      ...             DB, DF, DT <-- These are not available in BASIC.
      x&, LONG        DD
      x!, SINGLE      DD
      x#, DOUBLE      DQ

   User-Defined Types
   ------------------

   The elements in a user-defined type are stored contiguously in
   memory, one after the other.  When a BASIC user-defined type
   appears in an argument list, BASIC passes the address of the
   beginning element of the user-defined type.

   The routine that receives the user-defined type must know the
   format of the type beforehand.  The assembly-language routine
   should then expect to receive a pointer to a structure of this
   type.

   BASIC String Formats
   --------------------

   Variable-Length Strings
   -----------------------

   Variable-length strings in BASIC have 4-byte string descriptors:

        +-------------------------------------+
        |      Length      | Address (offset) |
        +-------------------------------------+
              (2 bytes)          (2 bytes)

   The first field of the string descriptor contains a 2-byte integer
   indicating the length of the actual string text.  The second field
   contains the address of the text.  This address is an offset into
   the default data area and is assigned by BASIC's string-space
   management routines.  These management routines need to be
   available to reassign this address whenever the length of the
   string changes, yet these management routines are only available to
   BASIC.  Therefore, an assembly-language routine should not alter
   the length of a BASIC variable-length string.

     Note: Fixed-length strings do not have a string descriptor.

   Passing Variable-Length Strings from BASIC
   ------------------------------------------

   When a BASIC variable-length string (such as A$)  appears in an
   argument list, BASIC passes a string descriptor rather than the
   string data itself.

     Warning:  When you pass a string from BASIC to assembly-
     language, the called routine should under no circumstances
     alter the length of the string.

   The routine that receives the string must not call any BASIC
   routine.  If it does, BASIC's string-space management routines may
   change the location of the string data without warning.

   The BASIC functions SADD and LEN extract parts of the string
   descriptor.  SADD extracts the address of the actual string data,
   and LEN extracts the length.  The results of these functions can
   then be passed to an assembly-language routine.

   BASIC should pass the result of the SADD function by value.  Bear
   in mind that the string's address, not the string itself, will be
   passed by value.  This amounts to passing the string itself by
   reference.  The BASIC module passes the string address, and the
   other module receives the string address.  The address returned by
   SADD is declared as type INTEGER, but is actually equivalent to a
   near pointer.

   There are two methods for passing a variable-length string from
   BASIC to assembly language.  The first method is to pass the string
   address and string length as separate arguments, using the SADD and
   LEN functions.  The second method is to pass the string descriptor
   itself, with a call statement such as the following:

      CALL CRoutine(A$)

   The assembly-language routine should then expect to receive a
   pointer to a string descriptor of this type.

   Passing String Descriptors from Assembly Language
   -------------------------------------------------

   To pass an assembly-language string to BASIC, first allocate a
   string in assembly language.  Then create a structure identical to
   a BASIC string descriptor.  Pass this structure by near reference.
   Make sure that the string originates in assembly language, not in
   BASIC.  Otherwise, BASIC may attempt to move the string around in
   memory.

     Warning: Microsoft does not recommend creating your own
     string descriptors in assembler functions because it is very
     easy to inadvertently destroy portions of the data segment.
     The BASIC routine should not reassign the value or length of
     a string passed from assembly language.

   The preferred method is to create the strings in BASIC and then
   modify their contents in the assembler function without altering
   their string descriptors.

   Fixed-Length Strings
   --------------------

   Fixed-length strings in BASIC are stored simply as contiguous bytes
   of characters, with no terminating character.  There is no string
   descriptor for a fixed-length string.

   To pass a fixed-length string to a routine, the string must be put
   into a user-defined type.  For example:

      TYPE FixType
         A AS STRING * 10
      END TYPE

   The string is then passed like any other user-defined type.

   Arrays
   ------

   There are several special problems that you need to be aware of
   when passing arrays between BASIC and assembly language:

   a. Arrays are implemented differently in BASIC, so that you must
      take special precautions when passing an array from BASIC to
      assembly language.

   b. Arrays are declared differently in assembly language and BASIC.

   c. Because BASIC uses an array descriptor, passed arrays must be
      created in BASIC.

   Passing Arrays from BASIC
   -------------------------

   To pass an array to an assembly-language routine, pass only the
   base element, and the other elements will be contiguous from there.
   You should pass only static arrays in QuickBASIC Versions 1.x.  In
   Versions 2.00 and later, you can pass both static and dynamic
   arrays.

   Do not attempt to use the "array descriptor" mentioned on Page 88
   of the "Microsoft QuickBASIC Compiler" Versions 1.x manual, as you
   cannot depend on the format of the descriptor to be the same from
   version to version. A consequence of this is that you cannot pass
   dynamic arrays to assembler through COMMON in QuickBASIC Version
   1.00, 1.01, 1.02, 2.00, 2.01, or 3.00. In Versions 2.x and 3.00,
   you can pass dynamic arrays only through CALL parameters.

   In QuickBASIC Versions 2.x and 3.00, the segment and offset of
   dynamic numeric arrays can be obtained using the PTR86 function
   described on Page 150 of the "Microsoft QuickBASIC Compiler"
   manual, and then that segment and offset can be passed in a CALL
   argument to the assembly-language routine.

   The base element of static arrays and dynamic string arrays can be
   passed to the assembly-language routine in Versions 2.x and 3.00.
   Array elements, or 4-byte string descriptors for an array of
   variable-length strings, will be contiguous in memory from the
   first element.

     Note: In Versions 2.00 and later, the SADD function was
     added to return the actual address of a variable-length
     string in string space in the default data segment.

   Passed Arrays Must Be Created in BASIC
   --------------------------------------

   BASIC keeps track of all arrays in a special structure called an
   array descriptor.  The array descriptor is unique to BASIC and is
   not available in any other language.  Because of this, to pass an
   array from assembly language to BASIC, the array must first be
   created in BASIC, then passed to the assembly-language routine.
   The assembly-language routine may then alter the values in the
   array, but it cannot change the length of the array.

   The array descriptor is similar in some respects to a string
   descriptor.  The array descriptor is necessary because BASIC may
   shift the location of array data in memory.  Therefore, you can
   safely pass arrays from BASIC only if you follow three rules:

   a. Pass the array's address by applying the VARPTR function to the
      first element of the array and passing the result by value.  To
      pass the far address of the array, apply both the VARPTR and
      VARSEG functions and pass each result by value.  The assembler
      gets the address of the first element and considers it the
      address of the entire array.

   b. The routine that receives the array must not, under any
      circumstances, make a call back to BASIC.  If it does, then the
      location of the array may change, and the address that was
      passed to the routine will become meaningless.

   c. BASIC can pass any member of an array by value.  With this
      method, the above precautions do not apply.

   Array Ordering
   --------------

   There are two types of ordering: row-major and column-major.

   BASIC uses column-major ordering, in which the leftmost dimension
   changes fastest.  When you use BASIC with the BC command line, you
   can select the /R compile option, which specifies that row-major
   order is to be used, rather than column-major order.

   COMMON Blocks
   -------------

   You can pass individual members of a BASIC common block in an
   argument list, just as you can any data.  However, you can also
   give an assembly-language routine access to the entire common block
   at once.

   Assembly language can reference the items of a common block by
   first declaring a structure with fields that correspond to the
   common block variables.  Having defined a structure with the
   appropriate fields, the assembly-language routine must then get the
   address of the common block.

   To pass the address of the common block, pass the address of the
   first variable in the block.  The assembly-language routine should
   expect to receive a structure by reference.

   For named common blocks, there is an alternative method. In the
   assembly-language program, a segment is set up with the same name
   as the COMMON block and then grouped with DGROUP, as follows:

      BNAME SEGMENT 'BC_VARS'
         x dw 1 dup (?)
         y dw 1 dup (?)
         z dw 1 dup (?)
      BNAME ENDS
      
      DGROUP GROUP BNAME

   The above assembler code matches with the following BASIC code
   using a named COMMON block:

      DEFINT A-Z
      COMMON /BNAME/ x,y,z

   Passing arrays through the COMMON block is done in a similar
   fashion.  However, only static arrays can be passed to assembler
   through COMMON.

     Note: Microsoft does not support passing dynamic arrays
     through COMMON to assembler (since this depends upon a
     Microsoft proprietary dynamic array descriptor format that
     changes from version to version).  Dynamic arrays can be
     passed to assembler only as parameters in a CALL statment.

   When static arrays are used, the entire array is stored in the
   COMMON block.

7. HOW TO RETURN VALUES FROM ASSEMBLY-LANGUAGE FUNCTIONS


   Assembler "functions" are not called with the CALL statement; they
   are invoked on the right-hand side of an equal sign (=) in compiled
   BASIC.  When calling an assembly-language function from BASIC,
   either the passed variable or a pointer to the passed variable is
   returned in the AX register, as shown in the following chart:

   Data Type       How Value Is Returned
   ---------       ---------------------
   Integer         The value is placed in AX.
                   
   Long            The high-order portion is placed in DX. The low-
                   order portion is placed in AX.
                   
   Single          The value is placed in the location provided by
                   BASIC. The segment is DS and the offset can be
                   found at BP+6. The offset located in BP+6 should be
                   placed in AX before the function exits.
                   
   Double          The value is placed in the location provided by
                   BASIC. The segment is DS and the offset can be
                   found at BP+6. The offset located in BP+6 should be
                   placed in AX before the function exits.
                   
   Variable-
   Length String   Pointer to a descriptor (offset in AX, segment in
                   DX).

     Note: BASIC does not allow functions with a fixed-length-
     string type or a user-defined type.

8. DEBUGGING MIXED-LANGUAGE PROGRAMS


   Microsoft CodeView is very useful when trying to debug mixed-
   language programs.  With it you can trace through the source code
   of both assembly language and BASIC and watch variables in both
   languages.

   To compile programs for use with CodeView, use the /Zi switch on
   the compile line for both the assembler and the BASIC compiler.
   Then when linking, use the /CO switch.

   CodeView is a multilanguage source code debugger supplied with
   Microsoft BASIC Compiler Versions 6.00 and 6.00b, Microsoft C
   Optimizing Compiler Versions 5.00 and 5.10, Microsoft Macro
   Assembler Versions 5.00 and 5.10, and Microsoft FORTRAN Compiler
   Version 4.00 and 5.00.

9. COMPILING AND LINKING THE SAMPLE PROGRAMS


   Following is a series of examples, demonstrating the interlanguage
   calling capabilities between BASIC and assembler.

   When compiling the sample BASIC programs, use the following compile
   line:

      BC /O BASICprogramname;

   When compiling the sample MASM programs, use the following compile
   line for MASM 5.00 or 5.10:

      MASM Assemprogramname;

   Or, use the following compile line for QuickAssembler 2.01:

      QCL Assemprogramname;

   To link the programs together, use the following LINK line:

      LINK BASICprogramname Assemprogramname;

BASIC SUPPORTS MASM 5.10 UPDATE .MODEL AND PROC EXTENSIONS
----------------------------------------------------------

The Microsoft Macro Assembler Version 5.10 includes several new
features (not found in MASM Version 5.00 or earlier) that simplify
assembly-language routines linked with high-level language programs.
Two of these features are as follows:

1. An extension to the .MODEL directive that automatically sets up
   naming, calling, and return conventions for a given high-level
   language. For example:

      .MODEL MEDIUM,BASIC

2. A modification of the PROC directive that handles most of the
   procedure entry automatically. The PROC directive saves specified
   registers, defines text macros for passed arguments, and generates
   stack setup code on entry and stack tear-down code on exit.

These new features are supported by QuickBASIC Versions 4.00, 4.00b,
and 4.50 and Microsoft BASIC Compiler Versions 6.00 and 6.00b.

Section 5 of the "Microsoft Macro Assembler Version 5.1 Update" manual
discusses the new features.

PROBLEM CALLING ASSEMBLER ROUTINE WITH LABEL ON END DIRECTIVE
-------------------------------------------------------------

A QuickBASIC EXE program will hang at run time if it is LINKed to an
assembly-language routine that uses a label on the END directive. The
same programs execute successfully when run inside the QB.EXE editor
with the assembly-language routine in a Quick library.

Although versions of QuickBASIC prior to Version 4.00 allow a label on
the END directive in a LINKed assembly-language program, programs for
Versions 4.00 and 4.00b require you to have no label on the assembly-
language END directive.

If the label cannot be removed from the assembly-language END
directive, you can put the assembly-language module in a .LIB file
with LIB.EXE, then LINK as follows to make the .EXE program work
correctly:

   LINK <prog>,,,<BASIC runtime library>+<assembly-language .LIB file>

When the linker creates an executable program, it successively
examines each .OBJ file and determines whether that file has a
specified entry point. The first .OBJ file that specifies an entry
point is assumed by the linker to be the main program, and program
execution begins there.

In the assembly-language routines, the purpose of a label with an END
directive is to indicate to the linker the program's starting address
or entry point (where program execution is to start). Therefore, if no
entry point is found in the QuickBASIC routine, program execution will
begin in the assembly-language routines -- in effect, the BASIC code
is totally bypassed.

In previous versions of the QuickBASIC compiler, the QuickBASIC object
code contains an entry-point specifier. Therefore, by simply listing
QuickBASIC object files before the assembly-language object files on
the LINK command line, the linker recognizes that the QB program is
the main program.

However, in QuickBASIC Version 4.00, the entry-point information is no
longer in the object file; instead, it resides in the run-time module
(for example, BCOM40.LIB or BRUN40.LIB). Because these files are
LINKed after the BASIC and assembly-language .OBJ files, if the
assembly-language routine specifies an entry point, the linker will
incorrectly assume that program execution is to begin in the assembly-
language routine.

Results of testing with previous versions of QuickBASIC indicate that
the programs run successfully both inside the editor and as .EXE files
when compiled with Versions 2.00, 2.01, and 3.00 of the QuickBASIC
compiler.

Their are two workarounds to correct this problem in Version 4.00:

1. Remove the label on the END directive (that is, remove the entry-
   point specification in your assembly-language routine) and
   reassemble.

2. The assembler .OBJ module can be used successfully without removing
   the label from the END directive.  If the assembly-language routine
   cannot be changed, place the assembly-language routine into a .LIB
   file.

ASSEMBLER ROUTINES USING ES IN QB.EXE MUST SET ES AND DS EQUAL
--------------------------------------------------------------

If CALLed assembler routines do string manipulation and use the ES
register, then the results inside the QB.EXE editor may differ from
the executable .EXE program if the assembler routines assume the ES
and DS registers are equal.

As a general rule, the ES and DS registers should not be assumed to be
equal in QuickBASIC Versions 4.00 and later.

The ES and DS registers are equal for the executable program; however,
this is not a valid assumption within the QB.EXE editor. When working
within the QB.EXE editor, the assembler routines must explicitly set
ES equal to DS, as shown in the code example below.

   The following assembler code sets ES equal to DS:

      push bp
      mov bp, sp
      push es         'These three
      push ds         'lines set the
      pop es          'es register equal to ds
        .
        .             'body of program
        .
      pop es          'at end of program need to
      pop bp          'restore saved registers
      ret

QUICK LIBRARY WITH 0 (ZERO) BYTES IN FIRST CODE SEGMENT
-------------------------------------------------------

A Quick library containing leading zeros in the first CODE segment is
invalid, causing the message "Error in loading file <name> - Invalid
format" when you try to load it in QuickBASIC. For example, this error
can occur if an assembly-language routine puts data that is
initialized to 0 (zero) in the first CODE segment, and it is
subsequently listed first on the LINK command line when you make a
Quick library.  If you have this problem, do either of the following:

1. Link with a BASIC module first on the LINK command line.

or

2. Make sure that, in whatever module comes first on the LINK command
   line, the first code segment starts with a nonzero byte.

CHAIN AND REFERENCES TO DGROUP
------------------------------

For mixed-language programs that use the CHAIN command, you should
make sure that any code built into an extended run-time module does
not contain any references to DGROUP. (The CHAIN command causes DGROUP
to move, but does not update references to DGROUP.) This rule applies
only to mixed-language programs; because BASIC routines never refer to
DGROUP, you can ignore this caution for programs written entirely in
BASIC.

To avoid this problem, you can use the value of SS, since BASIC always
assumes that SS coincides with DGROUP.

CALLING DOS I/O ROUTINES DOES NOT AFFECT QB CURSOR POSITION
-----------------------------------------------------------

MASM routines linked with a QuickBASIC program that do screen output
(by DOS interrupts) do not update the cursor position after returning
to the calling QuickBASIC program. (Note: This also applies to using
CALL INTERRUPT statements in QuickBASIC.)

For example, after the following three steps, the next PRINT statement
goes directly after the last QB PRINT statement, ignoring the new line
position from calling the MASM routine:

1. Do a PRINT from QuickBASIC.

2. CALL a MASM routine that does some DOS display string functions
   (INT 21h, function 09H).

3. Return to QuickBASIC.

This is expected behavior. Assembly-language routines should not
change the BASIC cursor position.

SOME COPROCESSOR ASSEMBLER INSTRUCTIONS ARE NOT EMULATED
--------------------------------------------------------

The Microsoft Macro Assembler Version 5.10 does not come with routines
to emulate a math coprocessor.

Page 382 of the "Microsoft Macro Assembler 5.10: Programmer's Guide"
states that to emulate math-coprocessor instructions, you must link
with a Microsoft high-level language that supports floating-point
emulation of the coprocessor. You would write the assembler procedure
using coprocessor instructions, then assemble with the /E option, and
finally link it with the high-level-language modules.

However, only a subset of coprocessor instructions is emulated by the
Microsoft high-level languages.

If you link your Microsoft higher-level language to an assembler
routine that invokes an instruction that is NOT emulated by the
higher-level language, then the program gives a run-time error (or
possibly hangs or gives incorrect results) when run on a machine that
has no coprocessor.

Below is a list of the coprocessor (8087 or 80287) instructions that
are not emulated by Microsoft high-level languages:

      Coprocessor
      Instruction        Definition
      -----------        ----------
      FBLD               Packed decimal load
      FBSTP              Packed decimal store and pop
      FCOS               Cosine function
      FDECSTP            Decrement stack pointer
      FINCSTP            Increment stack pointer
      FINIT              Initialize processor
      FLDENV             Load environment
      FNOP               No operation
      FPREM1             Partial remainder
      FRSTOR             Restore saved state
      FSAVE              Save state
      FSETPM             Set protected mode
      FSIN               Only sine function
      FSINCOS            Sine and cosine function
      FSTENV             Store environment
      FUCOM              Unordered comparison
      FUCOMP             Unordered comparison and pop
      FUCOMPP            Unordered comparison and double pop
      FXTRACT            Extract exponent and significant


Also, some of the no-wait forms of instructions are not emulated, such
as FNSTENV and FNINIT.

Common Pitfalls
---------------

The following common pitfalls are all explained in more detail in the
main text.  This list just supplies a simple checklist to go over when
you encounter problems doing mixed-language programming.

1. Make certain the version numbers of the two languages are
   compatible:

   The following table specifies which versions of Microsoft BASIC can
   be linked with specific versions of Microsoft Macro Assembler or
   QuickAssembler:

                    BASIC
      QuickBASIC    Compiler  <->  MASM      QuickAssembler
      ----------    --------  ---  ----      --------------
      4.00          --        <->  5.00 or   --
      --            6.00      <->  5.10 or   --
      4.00b    or   6.00b     <->  5.10 or   2.01
      4.50          --        <->  5.10 or   2.01

2. Make certain all registers that need to be saved are preserved.
   There are several registers that need to be preserved in a mixed-
   language program.  These registers are as follows:

      CX, BX
      BP, SI, DI, SP
      CS, DS, SS, ES

   The direction flag should also be preserved (for safety, all the
   flags should be preserved).

   On the 80286 and 80386 all extended registers should also be
   preserved.

3. When passing strings to assembly language watch for two things:

   a. SADD should be used instead of VARPTR when passing variable-
      length strings to assembly language.  VARPTR will return the
      offset to the string descriptor, not to the string itself.

   b. The assembly-language routine must not, under any circumstances,
      alter the string descriptor in any way.

4. When using VARSEG, VARPTR, or SADD to pass addresses to assembly
   language, it is important to check the function definition.  Since
   BASIC normally passes all parameters by reference, any parameter
   that is an address should be declared using BYVAL.  If BYVAL is not
   used, BASIC will create a temporary variable to hold the address,
   then pass a pointer to this variable (in effect, pass a pointer to
   a pointer).

5. Make certain there is not a label on the END directive in the
   assembly-language routine.

6. If the routine works outside the environment but doesn't work in a
   Quick library, then check to make certain that ES is not assumed to
   be equal to DS.

7. If updating from QuickBASIC Version 3.00 or earlier to 4.00 or
   later, there are several things to watch for:

   a. The string descriptor changed between Version 3.00 and 4.00 of
      QuickBASIC.  Any assembly-language routines that deal with the
      string descriptor should be updated to use the new string
      descriptor.

   b. In QuickBASIC Versions 3.00 and earlier, it did not matter if
      some registers were not preserved (such as SI and DI);
      therefore, routines that didn't preserve these registers would
      still run.  These registers must be preserved to be used with
      QuickBASIC Versions 4.00 and later.

PASSING NUMERIC VARIABLESS FROM BASIC TO ASSEMBLY BY NEAR REFERENCE
-------------------------------------------------------------------

BASIC
-----

DECLARE SUB Numint(i%)
DECLARE SUB Numlong(lng&)
DECLARE SUB Numsng(s!)
DECLARE SUB Numdbl(d#)

i% = 2
l& = 4
s! = 3.4
d# = 5.6

CLS
PRINT "         BEFORE","AFTER"
PRINT "Integer: ";i%,,
CALL Numint(i%)
PRINT i%

PRINT "Long   : ";HEX$(lng&),,
CALL Numlong(lng&)
PRINT HEX$(lng&)

PRINT "Single : ";s!,
CALL Numsng(s!)
PRINT s!

PRINT USING "Double : ##.####            ";d#,
CALL Numdbl(d#)
PRINT USING "##.####"; d#

END


Assembly
--------

.MODEL MEDIUM, BASIC
.CODE
        PUBLIC Numint, Numlong, Numsng, Numdbl
Numint  PROC
        push bp
        mov bp, sp        ; set stack frame
        mov bx, [bp+6]
        mov ax, ds:[bx]   ; get integer
        shl ax, 1         ; multiply by 2
        mov ds:[bx], ax   ; put new value back
        pop bp
        ret 4
Numint  ENDP
Numlong PROC        push bp
        mov bp, sp        ; set stack frame
        mov bx, [bp+6]
        mov cx, ds:[bx]   ; get long
        mov ax, ds:[bx+2] ; switch high and low words
        mov ds:[bx+2], cx ; put new value back
        mov ds:[bx], ax
        pop bp
        ret 4
Numlong ENDP

Numsng  PROC
        push bp
        mov bp, sp        ; set stack frame
        mov bx, [bp+6]
        mov ax, ds:[bx+2] ; get single
        or ah, 80h        ; set sign bit
        mov ds:[bx+2], ax ; put new value back
        pop bp
        ret 4
Numsng  ENDP

Numdbl  PROC
        push bp
        mov bp, sp         ; set stack frame
        mov bx, [bp+6]
        mov ax, ds:[bx+6]  ; get double
        or ah, 80h         ; set sign bit
        mov ds:[bx+6], ax  ; put new value back
        pop bp
        ret 4
Numdbl  ENDP
        END


Output
------

          BEFORE    AFTER
Integer:   2         4
Long   :  4         40000
Single :   3.4      -3.4
Double :   5.6000   -5.6000

PASSING NUMERIC VARIABLES FROM BASIC TO ASSEMBLY BY FAR REFERENCE
-----------------------------------------------------------------

BASIC
-----

DECLARE SUB Numint(SEG i%)
DECLARE SUB Numlong(SEG lng&)
DECLARE SUB Numsng(SEG s!)
DECLARE SUB Numdbl(SEG d#)

i% = 2
lng& = 4
s! = 3.4
d# = 5.6

CLS
PRINT "         BEFORE","AFTER"
PRINT "Integer: ";i%,,
CALL Numint(i%)
PRINT i%

PRINT "Long   : ";HEX$(lng&),,
CALL Numlong(lng&)
PRINT HEX$(lng&)

PRINT "Single : ";s!,
CALL Numsng(s!)
PRINT s!

PRINT USING "Double : ##.####            ";d#,
CALL Numdbl(d#)
PRINT USING "##.####"; d#
END


Assembly
--------

.MODEL MEDIUM, BASIC
.CODE
        PUBLIC Numint, Numlong, Numsng, Numdbl
Numint  PROC
        push bp
        mov bp, sp        ; set stack frame
        push es
        mov es, [bp+8]    ; get seg
        mov bx, [bp+6]    ; get offset
        mov ax, es:[bx]   ; get actual integer
        shl ax, 1         ; multiply by 2
        mov es:[bx], ax   ; put back new value
        pop es
        pop bp
        ret 4
Numint  ENDP
Numlong PROC        push bp
        mov bp, sp        ; set stack frame
        push es
        mov es, [bp+8]    ; get seg
        mov bx, [bp+6]    ; get offset
        mov cx, es:[bx]   ; get actual long
        mov ax, es:[bx+2] ; switch high and low words
        mov es:[bx+2], cx ; put back new value
        mov es:[bx], ax
        pop es
        pop bp
        ret 4
Numlong ENDP

Numsng  PROC
        push bp
        mov bp, sp        ; set stack frame
        push es
        mov es, [bp+8]    ; get seg
        mov bx, [bp+6]    ; get offset
        mov ax, es:[bx+2] ; get actual single
        or ah, 80h        ; set sign bit
        mov es:[bx+2], ax ; put back new value
        pop es
        pop bp
        ret 4
Numsng  ENDP

Numdbl  PROC
        push bp
        mov bp, sp         ; set stack frame
        push es
        mov es, [bp+8]     ; get seg
        mov bx, [bp+6]     ; get offset
        mov ax, es:[bx+6]  ; get actual double
        or ah, 80h         ; set sign bit
        mov es:[bx+6], ax  ; put back new value
        pop es
        pop bp
        ret 4
Numdbl  ENDP
        END


Output
------

          BEFORE    AFTER
Integer:   2         4
Long   :  4         40000
Single :   3.4      -3.4
Double :   5.6000   -5.6000

PASSING NUMERIC VARIABLES FROM BASIC TO ASSEMBLY BY VALUE
---------------------------------------------------------

BASIC
-----

DECLARE SUB ValInt(BYVAL i%)
DECLARE SUB ValLong(BYVAL lng&)

i% = ASC("A")
lng& = ASC("B") * 65536 + ASC("C")

CLS
CALL ValInt(i%)
CALL ValLong(lng&)

END


Assembly
--------

.MODEL MEDIUM, BASIC
.CODE
        PUBLIC ValInt, ValLong
ValInt  PROC
        push bp
        mov bp, sp      ; set stack frame
        mov dx, [bp+6]  ; get integer
        mov ah, 02      ; DOS interrupt to print character
        int 21h
        pop bp
        ret 2
ValInt  ENDP

ValLong PROC
        push bp
        mov bp, sp      ; set stack frame
        mov dx, [bp+6]  ; get first part of long
        mov ah, 02      ; DOS interrupt to print character
        int 21h
        mov dx, [bp+8]  ; get second part of long
        int 21h         ; print it
        pop bp
        ret 4
ValLong ENDP
        END


Output
------

ABC

PASSING NUMERIC VARIABLES FROM ASSEMBLY TO BASIC
------------------------------------------------

BASIC
-----

DECLARE SUB AssemSub(dummy AS INTEGER)

CALL AssemSub(dummy%)
END

SUB NumInt(i AS INTEGER)
   PRINT "Integer : "; i
END SUB

SUB NumLong(lng AS LONG)
   PRINT "Long    : "; lng
END SUB

SUB NumSingle(s AS SINGLE)
   PRINT "Single  : "; s
END SUB

SUB NumDouble(d AS DOUBLE)
   PRINT "Double  : "; d
END SUB


Assembly
--------

.MODEL MEDIUM, BASIC
.DATA
         intnum dw 32767           ; initialize data
         Longnum dd 37999
         Singlenum dd 123.45
         Doublenum dq 1234.14159

.CODE
         EXTRN NumInt:PROC         ; declare BASIC procedures
         EXTRN NumLong:PROC
         EXTRN NumSingle:PROC
         EXTRN NumDouble:PROC

         PUBLIC AssemSub
AssemSub PROC
         push bp
         mov bp, sp

         mov ax, OFFSET intnum     ; get address of integer
         push ax
         call NumInt

         mov ax, OFFSET Longnum    ; get address of long
         push ax
         call NumLong

         mov ax, OFFSET Singlenum  ; get address of single
         push ax
         call NumSingle

         mov ax, OFFSET Doublenum  ; get address of double
         push ax
         call NumDouble

         pop bp
         ret 2
AssemSub ENDP

         END


Output
------

Integer : 32767
Long    : 37999
Single  : 123.45
Double  : 1234.14159

PASSING A VARIABLE-LENGTH STRING TO ASSEMBLY BY NEAR REFERENCE
--------------------------------------------------------------

BASIC
-----

DECLARE SUB RString(BYVAL soff AS INTEGER)

A$ = "This is the string" + "$"  ' "$" terminates string for INT call

CALL RString(SADD(A$))

END


Assembly
--------

.MODEL MEDIUM
.CODE
        PUBLIC RString
RString PROC
        push bp
        mov bp, sp      ; set stack frame
        mov dx, [bp+6]  ; get offset to string
        mov ah, 9       ; DOS interrupt to print string
        int 21h
        pop bp
        ret 2
RString ENDP
        END


Output
------

This is the string

PASSING A VARIABLE-LENGTH STRING TO ASSEMBLY BY FAR REFERENCE
-------------------------------------------------------------

BASIC
-----

DECLARE SUB PSTRING(BYVAL STRSEG AS INTEGER, BYVAL STROFF AS INTEGER)

A$ = "Hello World"
PRINT "Before call: ";
PRINT A$
CALL PSTRING(VARSEG(A$), SADD(A$))
PRINT "After call : ";
PRINT A$

Assembly
--------

; Note: This routine uses the MASM 5.10 update PROC extensions

DOSSEG
.MODEL MEDIUM, PASCAL
.CODE

pstring PROC sseg:WORD, soff:WORD
        push bx                  ; save bp register and dx
        push dx

        mov ax, sseg             ; get segment of string
        mov ds, ax               ; put into segment register
        mov ax, soff             ; get offset of string
        mov bx, ax
        mov al, 65               ; 65 = ascii 'A'
        mov BYTE PTR ds:[bx], al ; move the 'A' to the first charcter
                                 ;   in the string
        pop dx
        pop bx                   ; restore dx and bp
        ret
pstring ENDP
        END

Output
------

Before call: Hello World
After call : Aello World

PASSING A BASIC STRING DESCRIPTOR TO ASSEMBLY BY NEAR REFERENCE
---------------------------------------------------------------

BASIC
-----

DECLARE SUB RString(A AS STRING)

A$ = "This is the String" + "$"  ' "$" terminates the string for INT
call
CALL RString(A$)

END


Assembly
--------

.MODEL MEDIUM, BASIC
.CODE
        PUBLIC RString
RString PROC
        push bp
        mov bp, sp     ; set stack frame
        mov bx, [bp+6] ; get offset of string descriptor
        mov dx, [bx+2] ; get address of string
        mov ah, 9      ; int call to print string
        int 21h
        pop bp

        ret 2
RString ENDP

        END


Output
------

This is the String

PASSING A BASIC STRING DESCRIPTOR TO ASSEMBLY BY FAR REFERENCE
--------------------------------------------------------------

BASIC
-----

A$ = "This is the String" + "$"  ' "$" terminates the string for INT
call
CALLS RString(A$)   ' Note: CALLS makes this pass seg and offset

END


Assembly
--------

.MODEL MEDIUM, BASIC
.CODE
        PUBLIC RString
RString PROC
        push bp
        mov bp, sp     ; set stack frame
        push ds
        mov ds, [bp+8] ; segment of descriptor
        mov bx, [bp+6] ; offset of descriptor
        mov dx, [bx+2] ; address of actual string
        mov ah, 9      ; DOS interrupt to print string
        int 21h
        pop ds
        pop bp

        ret 4
RString ENDP

        END


Output
------

This is the String

PASSING A BASIC STRING DESCRIPTOR TO BASIC FROM ASSEMBLY
--------------------------------------------------------

BASIC
-----

DECLARE SUB MkString

CALL MkString

END

SUB BasicSub(TheString AS STRING)
   PRINT LEN(TheString)
   PRINT TheString
END SUB


Assembly
--------

.MODEL MEDIUM
         SType STRUC    ; this structure defines a string descriptor
               SLength DW 18
               Soff    DW ?
         SType ENDS
.DATA
         StringDesc SType <>
         TheString  DB 'This is the string'

.CODE
         EXTRN BasicSub:PROC

         PUBLIC MkString
MkString PROC
         mov ax, OFFSET TheString        ; set up string descriptor
         mov bx, OFFSET StringDesc.Soff
         mov [bx], ax
         mov ax, OFFSET StringDesc.SLength
         push ax        ; pass address of descriptor to BASIC
         CALL BasicSub
         ret
MkString ENDP
         END


Output
------

 18
This is the string

PASSING A BASIC FIXED-LENGTH STRING TO AND FROM ASSEMBLY BY NEAR
REFERENCE
----------------------------------------------------------------

BASIC
-----

DECLARE SUB RString(BYVAL offs AS INTEGER)

TYPE fixstring
   s AS STRING * 20
END TYPE

DIM a AS STRING * 20

CLS
a = "BASIC String$" ' "$" terminates string for assembly

CALL RString(VARPTR(a))
END

SUB BasicSub(a AS fixstring)
   LOCATE 2, 1        ' because print in assembly won't move BASIC's
   PRINT a.s          '   screen position
END SUB


Assembly
--------

.MODEL MEDIUM, BASIC
.DATA
  astr  DB 'Assembly String      '

.CODE
        EXTRN BasicSub:PROC

        PUBLIC RString
RString PROC
        push bp
        mov bp, sp           ; set stack frame
        mov dx, [bp+6]       ; address of string
        mov ah, 9            ; DOS interrupt to print string
        int 21h

        mov ax, OFFSET astr  ; address of assembly string
        push ax              ; pass it to BASIC
        call BasicSub

        pop bp
        ret 2
RString ENDP
        END


Output
------

BASIC String
Assembly String

PASSING A BASIC FIXED-LENGTH STRING TO ASSEMBLY BY FAR REFERENCE
----------------------------------------------------------------

BASIC
-----

DECLARE SUB RString(BYVAL sseg AS INTEGER, BYVAL soff AS INTEGER)

DIM a AS STRING * 20

CLS
a = "BASIC String$" ' "$" terminates string for assembly

CALL RString(VARSEG(a), VARPTR(a))

END


Assembly
--------

.MODEL MEDIUM, BASIC
.CODE
        PUBLIC RString
RString PROC
        push bp
        mov bp, sp      ; set stack frame
        push ds
        mov ds, [bp+8]  ; segment of string
        mov dx, [bp+6]  ; offset of string
        mov ah, 9       ; DOS interrupt to print string
        int 21h
        pop ds
        pop bp
        ret 4
RString ENDP
        END


Output
------

BASIC String

PASSING A USER-DEFINED TYPE FROM BASIC TO ASSEMBLY BY NEAR REFERENCE
--------------------------------------------------------------------

BASIC
-----

DEFINT A-Z

TYPE mixed
   i AS INTEGER
   l AS LONG
   s AS SINGLE
   d AS DOUBLE
   fx AS STRING * 19
END TYPE

DECLARE SUB MasmSub (dummy AS mixed)

DIM dummy AS mixed

CLS
PRINT "Calling assembly routine to fill the user-defined
type."
CALL MasmSub(dummy)
PRINT "Values in user-defined type:"

PRINT "Integer: ", dummy.i
PRINT "Long: ", dummy.l
PRINT "Single: ", dummy.s
PRINT "Double: ", dummy.d
PRINT "fixed-length String: ", dummy.fx

END


Assembly
--------

.MODEL MEDIUM
        usrdefType STRUC
                   iAsm  DW 10
                   lAsm  DD 43210
                   sAsm  DD 32.10
                   dAsm  DQ 12345.67
                   fxAsm DB 'Fixed-length string'
        usrdefType ENDS
.DATA
        AsmRec usrdefType <>

        PUBLIC MasmSub
MasmSub PROC
        push bp
        mov  bp,sp            ; set stack frame
        push es
        push di
        push si
        push cx
        push ds
        pop  es

        mov di,[bp+6]         ; get offset of user-defined type
        mov si,OFFSET AsmRec  ; set up for copy
        mov cx,37             ; size of structure
        rep movsb             ; copy values to BASIC
variable

        pop cx
        pop si
        pop di
        pop es
        pop bp
        ret 2
MasmSub ENDP
        END


Output
------

     Integer:   10
     Long:      43210
     Single:    32.10
     Double:    12345.67
     fixed-length String:  Fixed-length string

PASSING A USER-DEFINED TYPE FROM BASIC TO ASSEMBLY BY FAR REFERENCE
-------------------------------------------------------------------

BASIC
-----

DEFINT A-Z
DECLARE SUB MasmSub (BYVAL segment, BYVAL offset)

TYPE mixed
   i AS INTEGER
   lng AS LONG
   s AS SINGLE
   d AS DOUBLE
   fx AS STRING * 19
END TYPE

DIM dummy AS mixed

CLS
PRINT "Calling assembly routine to fill the user-defined type."
CALL MasmSub(VARSEG(dummy), VARPTR(dummy))
PRINT "Values in user-defined type:"

PRINT "Integer: ", dummy.i
PRINT "Long: ", dummy.lng
PRINT "Single: ", dummy.s
PRINT "Double: ", dummy.d
PRINT "fixed-length String: ", dummy.fx

END


Assembly
--------

.MODEL MEDIUM
        usrdefType STRUC
                   iAsm  DW 10
                   lAsm  DD 43210
                   sAsm  DD 32.10
                   dAsm  DQ 12345.67
                   fxAsm DB 'Fixed-length string'
        usrdefType ENDS
.DATA
        AsmRec usrdefType <>

        PUBLIC MasmSub
MasmSub PROC FAR
        push bp
        mov  bp,sp
        push es
        push di
        push si
        push cx

        mov es,[bp+8]         ; get segment of user-defined type
        mov di,[bp+6]         ; get offset of user-defined type
        mov si,OFFSET AsmRec
        mov cx,37             ; size of structure
        rep movsb             ; copy values to BASIC variable

        pop cx
        pop si
        pop di
        pop es
        pop bp
        ret 4
MasmSub ENDP
        END


Output
------

     Integer:   10
     Long:      43210
     Single:    32.10
     Double     12345.67
     fixed-length String:  Fixed-length string

PASSING A USER-DEFINED TYPE FROM ASSEMBLY TO BASIC
--------------------------------------------------

BASIC
-----

DEFINT A-Z
DECLARE SUB MasmSub

TYPE mixed
   i AS INTEGER
   lng AS LONG
   s AS SINGLE
   d AS DOUBLE
   fx AS STRING * 19
END TYPE

DIM dummy AS mixed

CLS
PRINT "Calling assembly routine which will fill the";
PRINT " user-defined type."
CALL MasmSub
END

SUB BASICSub (dummy AS mixed)

   PRINT "Values in user-defined type:"
   PRINT
   PRINT "Integer: ", dummy.i
   PRINT "Long: ", dummy.lng
   PRINT "Single: ", dummy.s
   PRINT "Double: ", dummy.d
   PRINT "fixed-length String: ", dummy.fx

END SUB


Assembly
--------

.MODEL MEDIUM
        usrdefType STRUC
                   iAsm  DW 10
                   lAsm  DD 43210
                   sAsm  DD 32.10
                   dAsm  DQ 12345.67
                   fxAsm DB 'Fixed-length string'
        usrdefType ENDS
.DATA
        BASICRec usrdefType <>
.CODE
        EXTRN BASICSub:PROC

        PUBLIC MasmSub
MasmSub PROC                     ; no stack frame is needed
                                 ;   because no arguments are
                                 ;   passed to assembly
        mov ax, OFFSET BASICRec  ; get address of structure
        push ax                  ; pass it as argument to BASIC
        CALL BASICSub
        ret
MasmSub ENDP
        END


Output
------

     Integer:   10
     Long:      43210
     Single:    32.10
     Double:    12345.67
     fixed-length String:  Fixed-length string

PASSING AN ARRAY OF INTEGERS FROM BASIC TO ASSEMBLY
---------------------------------------------------

BASIC
-----

DEFINT A-Z
DECLARE SUB MasmSub (BYVAL segment, BYVAL offset, BYVAL number)

'REM $DYNAMIC     'Can be either STATIC (the default) or DYNAMIC
DIM x%(1 TO 10)   'Remove comment to define array DYNAMICally

CLS
PRINT "Calling assembly routine to fill array elements..."
CALL MasmSub(VARSEG(x%(1)), VARPTR(x%(1)), 10)
PRINT "Values in array:"

FOR i = 1 TO 10
    PRINT x%(i);
NEXT

END

Assembly
--------

.MODEL MEDIUM
.CODE
        PUBLIC MasmSub
MasmSub PROC            ; can use proc far here too
        push bp         ; save registers for BASIC
        mov  bp,sp      ; get the stack pointer

        mov  es,[bp+10] ; get segment of array
        mov  bx,[bp+8]  ; get offset of array

        mov  cx,[bp+6]  ; get length of array
        mov  al,1       ; fill array elements with 1's

next:   mov  es:[bx],al ; put one in the array element
        add  bx,2       ; increment counter to next array element
                        ; -- add two bytes for integers, four bytes
                        ; -- for single precision and long integers,
                        ; -- and 8 bytes for double precision numbers
        loop next       ; loop to assign next array element
        pop  bp         ; restore bp for BASIC
        ret  6          ; restore stack
MasmSub ENDP
        END


Output
------

  1 1 1 1 1 1 1 1 1 1

PASSING AN ARRAY OF LONG INTEGERS FROM BASIC TO ASSEMBLY
--------------------------------------------------------

BASIC
-----

REM Program that calls an assembly routine that fills each
REM element with a 1.

DEFINT A-Z
DECLARE SUB MasmSub (BYVAL segment, BYVAL offset, BYVAL number)

'REM $DYNAMIC       'Can be either STATIC (the default) or DYNAMIC
DIM lng&(1 TO 10)   'Remove comment to define array DYNAMICally

CLS
PRINT "Calling assembly routine to fill array elements..."
CALL MasmSub(VARSEG(lng&(1)), VARPTR(lng&(1)), 10)
PRINT "Values in array:"

FOR i% = 1 TO 10
    PRINT lng&(i);
NEXT
END


Assembly
--------

.MODEL MEDIUM
.CODE
        PUBLIC MasmSub
MasmSub PROC             ; can use proc far here too
        push bp          ; save registers for BASIC
        mov bp, sp

        mov es, [bp+10]  ; get segment of array
        mov bx, [bp+8]   ; get offset of array

        mov cx, [bp+6]   ; get length of array
        mov al, 1

next:   mov es:[bx], al  ; put one in the array element
        add bx, 4        ; increment counter to next array element
        loop next        ; loop to assign next array element
        pop bp           ; restore bp for BASIC
        ret 6
MasmSub ENDP
        END


Output
------

 1 1 1 1 1 1 1 1 1 1

PASSING AN ARRAY OF SINGLE-PRECISION VARIABLES FROM BASIC TO ASSEMBLY
---------------------------------------------------------------------

BASIC
-----

REM Program that calls an assembly routine that changes the
REM sign of an array of numbers
DEFINT A-Z
DECLARE SUB MasmSub (BYVAL segment, BYVAL offset, BYVAL number)
'REM $DYNAMIC     'Can be either STATIC (the default) or DYNAMIC
DIM s!(1 TO 10)   'Remove comment to define array DYNAMICally
FOR i% = 1 to 10
   s!(i%) = i%
NEXT
CLS
PRINT "Calling assembly routine to fill array elements..."
CALL MasmSub(VARSEG(s!(1)), VARPTR(s!(1)), 10)
PRINT "Values in array:"
FOR i% = 1 TO 10
    PRINT s!(i);
NEXT
END


Assembly
--------

.MODEL MEDIUM
.CODE
        PUBLIC MasmSub
MasmSub PROC             ; can use proc far here too
        push bp          ; save registers for BASIC
        mov bp, sp

        mov es, [bp+10]  ; get segment of array
        mov bx, [bp+8]   ; get offset of array
        add bx, 3        ; offset to byte holding sign bit
        mov cx, [bp+6]   ; get length of array
        mov al, 1

next:   mov al, BYTE PTR es:[bx]
        or al, 80h       ; set sign bit
        mov es:[bx], al
        add bx, 4        ; increment counter to next array element
        loop next        ; loop to assign next array element
        pop bp           ; restore bp for BASIC
        ret 6
MasmSub ENDP
        END

Output
------

-1 -2 -3 -4 -5 -6 -7 -8 -9 -10

PASSING AN ARRAY OF DOUBLE-PRECISION VARIABLES FROM BASIC TO ASSEMBLY
---------------------------------------------------------------------

BASIC
-----

DECLARE SUB FillDbl(BYVAL ASeg AS INTEGER, BYVAL AOff AS INTEGER)

DIM DblArray(1 TO 5) AS DOUBLE

CALL FillDbl(VARSEG(DblArray(1)), VARPTR(DblArray(1)))
FOR i% = 1 TO 5
   PRINT DblArray(i%)
NEXT
END


Assembly
--------

.MODEL MEDIUM, BASIC
.DATA
        Dbl1 DQ 123.45       ; initialize data table
        Dbl2 DQ 456.78
        Dbl3 DQ 98765.432
        Dbl4 DQ 12345.678
        Dbl5 DQ 777.888
.CODE
        PUBLIC FillDbl
FillDbl PROC
        push bp
        mov bp, sp           ; set stack frame
        push es

        mov es, [bp+8]       ; segment of array
        mov di, [bp+6]       ; offset of array
        mov si, OFFSET Dbl1  ; get offset of data table
        mov cx, 40           ; length of data in table
        rep movsb            ; copy data table to array

        pop es
        pop bp
        ret 4
FillDbl ENDP
        END


Output
------

123.45
456.78
98765.432
12345.678
777.888

PASSING AN ARRAY OF BASIC STRING DESCRIPTORS TO ASSEMBLY
--------------------------------------------------------

BASIC
-----

' This program demonstrates passing an array of strings
' to an assembly language routine.  The assembly language
' routine then receives the address of the array, and
' interprets the array as an array of string descriptors.
' It then uses the descriptors to get the length and address
' of the strings.  It uses these two values to uppercase all of
' the lowercase alphabetic characters in any of the
' strings, and to skip all others.
' It is very important to pass the assembly routine the number
' of elements in the array.

OPTION BASE 0
DECLARE SUB UpCaseArray (BYVAL ArrayAddress%, arraylen%)
' BYVAL is necessary because we want to pass the VALUE of
' the address, not a pointer to the address.
DIM Num%, Array1$(20)
CLS

WHILE NOT a$ = "quit"
   INPUT "Enter a string ('quit' to end): ", a$
   Array1$(Num%) = a$
   Num% = Num% + 1
WEND

CALL UpCaseArray(VARPTR(Array1$(0)), Num%)
CLS
FOR i% = 0 TO (Num% - 1)
   PRINT Array1$(i%)
NEXT
END


Assembly
--------

.MODEL MEDIUM,BASIC
.CODE
        PUBLIC UpCaseArray
UpCaseArray PROC FAR
        push bp
        mov  bp,sp
        push di
        mov bx,[bp+6]    ; Argument #2: Number of array elements.
        mov cx,[bx]      ; Get the actual number of array elements.
        jcxz EndOutLoop  ; If the array has 0 elements then quit.
        mov bx,[bp+8]    ; Argument #1: Pointer to an array of
                         ; descriptors.
OutLoop:                 ; CX is the outer-OutLoop counter.
        push cx          ; Save the outer loop counter.
        mov cx,[bx]      ; Get the first two bytes of the current
                         ; descriptor, which is the string length.
        jcxz EndInLoop   ; If zero length, end the inner loop.
        mov di,[bx+2]    ; The second 2 bytes is the address.
                         ; DI = pointer to current string.
InLoop:                  ; Check if the char need to be UpCased.
        cmp byte ptr [di],'a'  ; Is it < a ?
        jb I1                  ; If so, then move to the next char.
        cmp byte ptr [di],'z'  ; Is is > z ?
        ja I1                  ; If so, then move on to the next char.
        and byte ptr [di],05Fh ; Make upper case. Mask -> (0101 1111).
I1:     inc di                 ; Move on to next character in the
                               ;    string.
        loop InLoop            ; Do it for all characters
                               ;    (until CX = 0).
                               ; Note: 'loop' decrements CX.
EndInLoop:
        add bx,4               ; Move on to next descriptor.
        pop cx                 ; Restore the outer loop counter.
        loop OutLoop           ; Do for all descriptors
                               ;    (until CX = 0).
EndOutLoop:
        pop di
        pop bp
        ret 4
UpCaseArray ENDP
        END


Output
------

     Enter a string ('quit' to end): First String
     Enter a string ('quit' to end): Second String
     Enter a string ('quit' to end): quit

     FIRST STRING
     SECOND STRING

PASSING DYNAMIC ARRAYS OF FIXED-LENGTH STRINGS TO ASSEMBLY
----------------------------------------------------------

The arrays being passed can be larger than 64K when the BASIC program
is compiled with the BC /AH option (or the QB.EXE editor is started
with the /AH option).

BASIC
-----

REM $DYNAMIC
DECLARE SUB Masm (
       BYVAL StrLength AS INTEGER,_
       BYVAL Length AS INTEGER,_
       BYVAL SegAddr1 AS INTEGER,_
       BYVAL Addr1 AS INTEGER,_
       BYVAL SegAddr2 AS INTEGER,_
       BYVAL Addr2 AS INTEGER)
Size% = 3%     'Size of the array (# of elements)
StrSize% = 11%  'Size of strings stored in array
CLS
DIM inArray(1 TO Size%) AS STRING * 11
DIM outArray(1 TO Size%) AS STRING * 11

'Load inArray with a 11 character string " *inArray* ":
FOR i = 1 TO Size%
  inArray(i) = " *inArray* "
NEXT i

' Masm will copy the contents of inArray to outArray:
CALL Masm(StrSize%,_
          Size%,_
          VARSEG(inArray(1)),_
          VARPTR(inArray(1)),_
          VARSEG(outArray(1)),_
          VARPTR(outArray(1)))

' Print the inArray:
PRINT
PRINT
PRINT "inArray: "

FOR i = 1 TO Size%
   PRINT inArray(i);
NEXT i

' Print the outArray to see that the contents of inArray
' were copied to it:

PRINT
PRINT "outArray: "

FOR i = 1 TO Size%
  PRINT outArray(i);
NEXT i
END

Assembly
--------

;***********************************************************
; The routine 'Masm' copies a dynamic string array of any
;   length to another string array.
; Warnings:
;   -- Arrays must be adequately dimensioned.
; Masm takes six parameters from the BASIC routine:
;   1 - Size of strings in array to be copied (BX)
;   2 - # of elements in array
;   3 - Segment of source array
;   4 - Offset of first element of source array
;   5 - Segment of destination array
;   6 - Offset of first element of destination array
;***********************************************************
.MODEL MEDIUM
.CODE
PUBLIC Masm

Masm    PROC
        push bp
        mov bp, sp

        mov bx, [bp+16]   ; Size of strings in array -> bx
        mov ax, [bp+14]   ; Elements in array -> ax
        mul bx            ; multiply ax by bx and put answer in ax
        mov cx, ax        ; Number of bytes in array -> cx

        mov es, [bp+12]   ; Segment of first array (inArray)
        mov bx, [bp+10]   ; Offset of first element in first
                          ; array
; body
        mov si, 0         ; initialize first array index (inArray)
again:
        mov al, es:[bx]   ; Load byte to copy to second array
                          ;  in al
        push bx           ; save bx
        push es           ; save es
        mov es, [bp+8]    ; Segment of second array (outArray)
        mov bx, [bp+6]    ; Offset of second arrays first
                          ; element
        add bx, si        ; Get correct offset into 2nd array from
                          ; index
        mov es:[bx], al   ; Move the byte into the second array
        pop es            ; restore es
        pop bx            ; restore bx
        add bx, 1         ; point to next element in first array
                          ; (inArray)
        add si, 1         ; increment second array (outArray) index
        loop again        ; Loop until cx is 0

        pop bp
        ret
Masm    ENDP
        END

Output
------

 *InArray*
 *InArray*
 *InArray*

PASSING A TWO-DIMENSIONAL INTEGER ARRAY FROM BASIC TO ASSEMBLY
--------------------------------------------------------------

BASIC
-----

DECLARE SUB TwoInt(BYVAL ASeg AS INTEGER, BYVAL AOff AS INTEGER)

DIM IntArray(1 TO 2, 1 TO 3) AS INTEGER

CALL TwoInt(VARSEG(IntArray(1, 1)), VARPTR(IntArray(1, 1)))
FOR row% = 1 TO 2
   FOR col% = 1 TO 3
      PRINT IntArray(row%, col%)
   NEXT
NEXT
END


Assembly
--------

.MODEL MEDIUM, BASIC
.DATA
        i11 DW 11           ; initialize data table
        i21 DW 21
        i12 DW 12
        i22 DW 22
        i13 DW 13
        i23 DW 23
.CODE
        PUBLIC TwoInt
TwoInt  PROC
        push bp
        mov bp, sp          ; set stack frame
        push es

        mov es, [bp+8]      ; segment of array
        mov di, [bp+6]      ; offset of array
        mov si, OFFSET i11
        mov cx, 6           ; number of items to copy
        rep movsw           ; copy data to array

        pop es
        pop bp
        ret 4
TwoInt  ENDP
        END


Output
------

11
12
13
21
22
23

PASSING A TWO-DIMENSIONAL FIXED-LENGTH STRING ARRAY FROM BASIC TO
ASSEMBLY
-----------------------------------------------------------------

BASIC
-----

DECLARE SUB TwoFix(BYVAL ASeg AS INTEGER, BYVAL AOff AS INTEGER)

DIM FixArray(1 TO 2, 1 TO 3) AS STRING * 9

CALL TwoFix(VARSEG(FixArray(1, 1)), VARPTR(FixArray(1, 1)))
FOR row% = 1 TO 2
   FOR col% = 1 TO 3
      PRINT FixArray(row%, col%)
   NEXT
NEXT
END


Assembly
--------

.MODEL MEDIUM, BASIC
.DATA
        Fix11 DB 'String 11'  ; allocate string data
        Fix21 DB 'String 21'
        Fix12 DB 'String 12'
        Fix22 DB 'String 22'
        Fix13 DB 'String 13'
        Fix23 DB 'String 23'
.CODE
        PUBLIC TwoFix
TwoFix  PROC
        push bp
        mov bp, sp            ; set stack frame
        push es

        mov es, [bp+8]        ; segment of string array
        mov di, [bp+6]        ; offset of string array
        mov si, OFFSET Fix11  ; get offset to string data
        mov cx, 54            ; length of all string data
        rep movsw             ; copy string data to array

        pop es
        pop bp
        ret 4
TwoFix  ENDP
        END


Output
------

String 11
String 12
String 13
String 21
String 22
String 23

PASSING A COMMON BLOCK FROM BASIC TO ASSEMBLY
---------------------------------------------

BASIC
-----

DECLARE SUB PutInfo ()
DIM b%(100)
COMMON /BNAME/ a%, b%(), c%

CALL PutInfo
CLS
PRINT a%, c%
FOR i = 0 TO 100
   PRINT b%(i);
NEXT i
END


Assembly
--------

.MODEL MEDIUM

BNAME segment COMMON 'BC_VARS'
   a  dw 1 dup (?)
   b  db 202 dup (?)      ;Note that all the space for the
                          ;array is set up
   c  dw 1 dup (?)
BNAME ends

DGROUP group BNAME

.CODE

        PUBLIC PutInfo
PutInfo PROC FAR
        push bp
        mov bp, sp               ; set stack frame

        inc word ptr dgroup:a    ; add 1 to a
        inc word ptr dgroup:c    ; add 1 to b

        mov cx, 101              ; value to initalize b% array
        mov di, offset dgroup:b

repeat:
        mov [di], cx             ; store value to b% array
        add di, 2                ; go to next integer variable
        loop repeat              ; note value in cx decremented
        pop bp
        ret
PutInfo ENDP
        END

Output
------

 1     2
101 100 99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81
80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59
58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37
36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15
14 13 12 11 10 9 8 7 6 5 4 3 2 1

     Note: When dynamic arrays are used, the array is not placed
     in the COMMON block. Instead, a multibyte array descriptor
     is placed in the COMMON block. The dynamic array descriptor
     changes from version to version, and is not released by
     Microsoft -- it is considered Microsoft proprietary
     information.

AN ASSEMBLY FUNCTION RETURNING AN INTEGER TO BASIC
--------------------------------------------------

BASIC
-----

DECLARE FUNCTION qprint%

FOR i = 1 TO 10
   x% = qprint%
   PRINT x%
NEXT i


Assembly
--------

.MODEL MEDIUM
.DATA
        shortnum dw 12345

.CODE
        PUBLIC QPrint
QPrint  PROC FAR

        push bp
        mov bp, sp

        mov bx, offset dgroup:shortnum
        mov ax, [bx]                     ; value is stored AX

        pop BP
        ret

QPrint  ENDP
        END


Output
------

 12345
 12345
 12345
 12345
 12345
 12345
 12345
 12345
 12345
 12345

AN ASSEMBLY FUNCTION RETURNING A LONG INTEGER TO BASIC
------------------------------------------------------

BASIC
-----

DECLARE FUNCTION qprint&

FOR i = 1 TO 10
   x& = qprint&
   PRINT x&
NEXT i

Assembly
--------

.MODEL MEDIUM
.DATA
        longnum dd 12345

.CODE
        PUBLIC  QPrint
QPrint  PROC FAR
        push bp
        mov bp, sp

        mov bx, offset dgroup:longnum
        mov dx, [bx+2]                   ; high order portion DX
        mov ax, [bx]                     ; low order portion AX

        pop bp
        ret

QPrint  ENDP
        END


Output
------

 12345
 12345
 12345
 12345
 12345
 12345
 12345
 12345
 12345
 12345

AN ASSEMBLY FUNCTION RETURNING A SINGLE-PRECISION VARIABLE TO BASIC
-------------------------------------------------------------------

BASIC
-----

DECLARE FUNCTION qprint!

FOR i = 1 TO 2
   x! = qprint!
   PRINT x!
NEXT i

Assembly
--------

.MODEL MEDIUM
.DATA
        singlenum DD 98.6

.CODE
        PUBLIC QPrint
QPrint  PROC FAR
        push bp
        mov bp, sp
        push es
        push si
        push di

        push ds   ; set es = ds
        pop es

        mov si, offset dgroup:singlenum
        mov di, [bp+6]  ;LOAD VALUE INTO ADDRESS AT BP+6
        mov cx, 4
        rep movsb

        mov ax, [bp+6]  ;LOAD OFFSET OF TEMP VALUE IN AX and
        mov dx, ss      ;SS into DX
        pop di
        pop si
        pop es
        pop BP
        ret 2

QPrint  ENDP
        END


Output
------

98.6
98.6

AN ASSEMBLY FUNCTION RETURNING A DOUBLE-PRECISION VARIABLE TO BASIC
-------------------------------------------------------------------

BASIC
-----

DECLARE FUNCTION qprint#
FOR i = 1 TO 2
   x# = qprint#
   PRINT x#
NEXT i


Assembly
--------

.MODEL MEDIUM
.DATA
        doublenum DQ 6765.89

.CODE
        PUBLIC QPrint
QPrint  PROC FAR
        push bp
        mov bp, sp
        push es
        push si
        push di

        push ds  ;set es==ds
        pop es

        mov si, offset dgroup:doublenum
        mov di, [bp+6]  ;LOAD VALUE INTO ADDRESS AT BP+6
        mov cx, 8
        rep movsb

        mov ax, [BP+6]  ;LOAD OFFSET OF TEMP VALUE IN AX and
        mov dx, ss      ;SS into DX

        pop di
        pop si
        pop es
        pop bp
        ret 2
QPrint  ENDP
        END


Output
------

6765.89
6765.89

AN ASSEMBLY FUNCTION RETURNING A VARIABLE-LENGTH STRING TO BASIC
----------------------------------------------------------------

BASIC
-----

DECLARE FUNCTION Qprint$ (i%)
CLS
FOR i% = 1 TO 3
   d$ = Qprint$(i%)    ' i% is the length of the string to be created.
   PRINT d$, LEN(d$)
NEXT


Assembly
--------

.MODEL MEDIUM
.DATA
        str      db 10 dup (?)  ;my own string
        mystring dw ?           ;my own descriptor (length)
                 dw ?           ;(offset)
.CODE
PUBLIC QPrint
QPrint PROC FAR
       push bp       ;save registers for BASIC
       mov bp, sp
       push ds
       push es
       mov bx, [bp+6] ;get the length off the stack
       mov cx, [bx]   ;and put it in CX
       mov ax, @data  ;load the segment into AX
       mov es, ax
       mov di, offset dgroup:str     ;load the offset into DI
       mov ax, 'aa'                  ; load character to fill
       rep stosb                     ;store "a" into the string
       mov bx, [bp+6]                ;put the length in CX again
       mov cx, [bx]
       mov ax, offset dgroup:str      ;put offset of string AX
       mov bx, offset dgroup:mystring ;put offset of descriptor in
                                     ;  BX
       mov [bx], cx                  ;length in first two bytes
       mov [bx+2], ax                ;offset into second two bytes
       lea ax, mystring              ;load address of descriptor
                                     ; into AX
       pop es
       pop ds
       pop bp        ;restore BP for BASIC
       ret 2         ;return skipping the passed parameters
QPrint ENDP
       END


Output
------

a     1
aa    2
aaa   3

USING SETMEM TO ALLOCATE SPACE FOR MEMORY ALLOCATION IN ASSEMBLY
----------------------------------------------------------------

BASIC
-----

DECLARE SUB AMem(BYVAL AllocSize AS INTEGER)

CLS
' Decrease the size of the far heap so AMem can use a DOS
' interrupt to get dynamic memory
BeforeCall% = SETMEM(-2048)
CALL AMem(1024%)
' Return the memory to the far heap; use a larger value so
' all space goes back into the heap.
AfterCall% = SETMEM(3500)

LOCATE 2, 1
IF AfterCall% <= BeforeCall% THEN
   PRINT "Memory not reallocated"
ELSE
   PRINT "Memory was successfully reallocated"
END IF

END


Assembly
--------

.MODEL MEDIUM, BASIC
.DATA
        Fail    DB 'Failed to allocate memory$'
        Success DB 'Succesfully allocated memory$'
.CODE
        PUBLIC AMem
AMem    PROC
        push bp
        mov bp, sp                ; set stack frame
        push cx
        push es

        mov ax, [bp+6]            ; get number of bytes free
        mov cl, 4                 ; divide by 16 to get number of
        shr ax, cl                ;   paragraphs of memory
        mov bx, ax
        mov ah, 48h
        int 21h                   ; DOS interrupt to allocate block
        mov es, ax                ;   of memory
        mov ah, 9
        jnc NoFail                ; carry flag clear if successful
        mov dx, OFFSET Fail       ; display failed message
        int 21h
        jmp Exit                  ; go back to BASIC

NoFail: mov dx, OFFSET Success    ; display success message
        int 21h
        mov ah, 49h
        int 21h

Exit:   pop es
        pop cx
        pop bp
        ret 2
AMem    ENDP
        END


Output
------

Successfully allocated memory
Memory was successfully reallocated


< END OF APPLICATION NOTE >
