Category : BASIC Source Code
Archive   : BAS2MASM.ZIP
Filename : BAS2MASM.TXT

 
Output of file : BAS2MASM.TXT contained in archive : BAS2MASM.ZIP
[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 ,,,+

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 - 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 >


  3 Responses to “Category : BASIC Source Code
Archive   : BAS2MASM.ZIP
Filename : BAS2MASM.TXT

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/