20.3.3 The Basic/MASM Interface

This section explains how to call MASM procedures or functions from Basic and how to receive Basic arguments for the MASM procedure. Pascal is the default naming and calling convention, so all lowercase letters are converted to uppercase. Routines defined with the FUNCTION keyword return values, but routines defined with SUB do not. Basic DEF FN functions and GOSUB routines cannot be called from another language.

The information provided pertains to Microsoft's Basic and QuickBasic compilers. Differences between the two compilers are noted when necessary.

Compatible Data Types

The list shows the Basic data types that are equivalent to the MASM 6.0 data types.

Basic Type Equivalent MASM Type  
STRING*1 WORD  
INTEGER (X%) SWORD  
SINGLE (X!) REAL4  
LONG (X&), CURRENCY SDWORD  
DOUBLE (X#) REAL8  

Naming Conventions

Basic recognizes up to 40 characters of a name. In the object code, Basic also drops any of its reserved characters: %, &, !, #, @, &.

Argument-Passing Defaults

Basic can pass data in several ways and can receive it by value or by near reference.

By default, Basic arguments are passed by near reference as two-byte addresses. To pass a near address, pass only the offset; if you need to pass a far address, pass the segment and offset separately as integer arguments. Pass the segment address first, unless you have specified C compatibility with the CDECL keyword.

Basic passes each argument in a call by far reference when CALLS is used to invoke a routine. You can also use SEG to modify a parameter in a preceding DECLARE statement so that Basic passes that argument by far reference.

To pass a Basic argument by value, apply the BYVAL keyword to the argument in the DECLARE statement. Arrays and user-defined types cannot be passed by value.

DECLARE SUB Test(BYVAL a%, b%, SEG c%)

CALL Test(x%, y%, z%)

CALLS Test(x%, y%, z%)

The CALL statement above passes the first argument (a%) by value, the second argument (b%) by near reference, and the third argument (c%) by far reference. The statement CALLS Test2(x%, y%, z%) passes each argument by far reference.

Changing the Calling Convention

Including the CDECL keyword in the Basic DECLARE statement enables the C calling and naming convention. This also allows a call to a MASM procedure with a varying number of arguments.

Equivalent Arrays

The DIM statement sets the number of dimensions for a Basic array and also sets the array's maximum subscript value. In the array declaration DIM x(a,b), the upper bounds (the maximum number of values possible) of the array are a and b. The default lower bound is 0. The default upper bound for an array subscript is 10.

Summary: Basic stores arrays in column-major order.

The default for column storage in Basic is column-major order, as in FORTRAN. For an array defined as DIM Arr%(3,3), reference the last element as Arr%(3,3). The first five elements of Arr (3,3) are

Arr(0,0), Arr(1,0), Arr(2,0), Arr(0,1), Arr(1,1)

When you pass an array from Basic to a language that expects arrays to be stored in row-major order, use the command-line option /R when compiling the Basic module.

Most Microsoft languages permit you to reference arrays directly. Basic uses an array descriptor, however, which is similar in some respects to a Basic string descriptor. The array descriptor is necessary because Basic may shift the location of array data in memory; Basic handles memory allocation for arrays dynamically.

Summary: To pass arrays to MASM, you need to follow several rules.

A reference to an array in Basic is really a near reference to an array descriptor. Array descriptors are always in DGROUP, even though the data may be in far memory. Array descriptors contain information about type, dimensions, and memory locations of data. You can safely pass arrays to MASM routines only if you follow three rules:

Pass the array's address by applying the VARPTR function to the first element of the Basic 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 receiving language gets the address of the first element and considers it to be the address of the entire array. It can then access the array with its normal array-indexing syntax.

If the MASM routine that receives the array makes a call back to Basic, then the location of the array data may change, and the address that was passed to the routine will be meaningless.

Basic can pass any member of an array by value. When passing individual array elements, the above restrictions do not apply.

You can apply LBOUND and UBOUND to a Basic array to determine lower and upper bounds, and then pass the results to another routine. This way, the size of the array does not need to be determined in advance.

String Format

Strings are stored in Basic as four-byte string descriptors, as shown below. The first field of the string descriptor contains a two-byte integer indicating the length of the actual string text. The second field contains the address of this text.

Summary: Basic's string descriptors are not compatible with the string formats of other languages.

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 available only to Basic. Therefore, your MASM procedure should not alter the length of a Basic string.

Prior to version 7.0 of the Microsoft Basic Compiler, there are two ways to pass strings:

1.Pass the address of the Basic string data to the other language

2.Mimic the form of the Basic string descriptor in the other language, then use that to access the string as Basic would access one of its own strings

NOTE:

Version 7.0 of the Microsoft Basic Compiler provides new functions that access the string descriptors and allow simplified string passing between Basic and other languages. Follow the instructions in the Basic documentation.

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 SADD function returns the address of a specified string variable. Basic should pass the result of the SADD function by value. Bear in mind that the string's address, not the string itself, is 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 C near pointer or Pascal ADR variable.

To return the far address of a string variable, version 7.0 (or later) of Basic provides the SSEGADD function. See your Basic documentation.

Summary: MASM can access data declared with a COMMON statement.

External Data

Variables can be global to modules in a Basic program by declaring them with the COMMON statement. Global variables do not require any additional declarations to be used by MASM procedures.

Structure Alignment

Basic packs user-defined types. For MASM structures to be compatible, select byte-alignment.

Summary: Use medium memory model with Basic.

Compiling and Linking

Always assemble the MASM module with medium model when you are linking to Basic. If you are listing other libraries on the LINK command line, specify Basic libraries first. (There are differences between the QBX and command-line compilation. See your Basic documentation.)

Returning Values

Basic follows the usual convention of returning values in AX or DX:AX. If the value is not floating point, an array, or a structured type, or if it is less than 4 bytes long, then the two-byte integers should be returned from the MASM procedure in AX and four-byte integers should be returned in DX:AX. For all other types, return the near offset in AX.

User-Defined Data Types

The Basic TYPE statement defines structures composed of individual fields. These types are equivalent to the C struct, FORTRAN record (declared with the STRUCTURE keyword), and Pascal Record types.

You can use any of the Basic data types except variable-length strings or dynamic arrays in a user-defined type. Once defined, Basic types can be passed only by reference.

Varying Number of Arguments

You can vary the number of arguments in a Basic routine only when you use CDECL to change the calling convention. To call a function with a varying number of arguments, you also need to suppress the type-checking that normally forces a call to be made with a fixed number of arguments. In Basic, you can remove this type checking by omitting a parameter list from the DECLARE statement.

Pointers and Addresses

VARSEG accesses a variable's segment address, and VARPTR accesses a variable's offset address. The values returned by these intrinsic Basic functions should then be passed or stored as ordinary integer variables. Pass segment addresses first unless your procedure specifies the cdecl calling convention. If you pass them to MASM procedures, pass by value. Otherwise you are attempting to pass the address of the address, rather than the address itself.

Example

This example calls the Power2 procedure in the MASM 6.0 module.

DEFINT A-Z

DECLARE FUNCTION Power2 (A AS INTEGER, B AS INTEGER)

PRINT "3 times 2 to the power of 5 is ";

PRINT Power2(3, 5)

END

The first argument, A, is higher in memory than B because Basic pushes arguments in the same order in which they appear.

Figure 20. 6 shows how the arguments are placed on the stack:

The assembly procedure can be written as follows:

.MODEL medium

Power2 PROTO PASCAL, Factor:PTR WORD, Power:PTR WORD

.CODE

Power2 PROC PASCAL, Factor:PTR WORD, Power:PTR WORD

mov bx, WORD PTR Factor ; Load Factor into

mov ax, [bx] ; AX

mov bx, WORD PTR Power ; Load Power into

mov cx, [bx] ; CX

shl ax, cl ; AX = AX * (2 to power

; of CX)

ret

Power2 ENDP

END

Note that each parameter must be loaded in a two-step process because the address of each is passed rather than the value. The return address is four bytes long because procedures called from Basic must be FAR.