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.
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 |
Basic recognizes up to 40 characters of a name. In the object code, Basic also drops any of its reserved characters: %, &, !, #, @, &.
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.
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.
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.
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.
Basic packs user-defined types. For MASM structures to be compatible, select byte-alignment.
Summary: Use medium memory model with Basic.
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.)
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.
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.
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.
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.
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.