Article ID: 71275
Article Last Modified on 11/21/2006
This article was previously published under Q71275
SUMMARY
The article below contains Part 2 of 2 of a complete tutorial and examples for passing all types of parameters between compiled Basic and Assembly Language.
The examples in BAS2MASM (but not the tutorial section) are also available in this database as multiple separate ENDUSER articles, which can be found as a group by querying on the word BAS2MASM.
MORE INFORMATION
See article 51501 for the text of Part 1 of "How to Pass Parameters Between Basic and Assembly Language." Part 2 is below.
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 (or custom) 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 or DS, since Basic always assumes that SS and DS coincide with DGROUP.
CALLING DOS I/O ROUTINES DOES NOT AFFECT QUICKBasic 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 QuickBasic PRINT statement, ignoring the new line position from calling the MASM routine:
- Do a PRINT from QuickBasic.
- CALL a MASM routine that does some DOS display string functions (INT 21h, function 09H).
- 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 higher-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. (Note: This is valid only under DOS; under OS/2, this causes a GP fault.)
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 higher-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.
APPENDIX B: COMMON PITFALLS
===============
The following common pitfalls are all explained in more detail in the main text. This list supplies a simple checklist to go over when you encounter problems doing mixed-language programming.
- 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 - When passing strings to assembly language, watch for two things:
- 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.
- The assembly-language routine must not, under any circumstances, alter the string descriptor in any way.
- 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).
- Make certain there is not a label on the END directive in the assembly-language routine.
- 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.
Note: ES and DS should never be assumed to be equal unless the assembly language routine explicitly sets them equal. - If updating from QuickBasic version 3.00 or earlier to 4.00 or later, there are several things to watch for:
- 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.
- In QuickBasic versions 3.00 and earlier, it doesn't matter if some registers are not preserved (such as SI and DI); therefore, routines that don't preserve these registers will still run. These registers must be preserved to be used with QuickBasic versions 4.00 and later.
PASSING NUMERIC VARIABLES 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 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 mov bx, [bp+6] mov ax, [bx] ; get integer shl ax, 1 ; multiply by 2 mov [bx], ax ; put new value back pop bp ret 2 Numint ENDP Numlong PROC push bp mov bp, sp ; set stack frame mov bx, [bp+6] mov cx, [bx] ; get long mov ax, [bx+2] ; switch high and low words mov [bx+2], cx ; put new value back mov [bx], ax pop bp ret 2 Numlong ENDP Numsng PROC push bp mov bp, sp ; set stack frame mov bx, [bp+6] or BYTE PTR [bx+2], 80h ; set sign bit pop bp ret 2 Numsng ENDP Numdbl PROC push bp mov bp, sp ; set stack frame mov bx, [bp+6] or BYTE PTR [bx+6], 80h ;set sign bit pop bp ret 2 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 EXTRN NumInt:PROC ; declare Basic procedures EXTRN NumLong:PROC EXTRN NumSingle:PROC EXTRN NumDouble:PROC .DATA intnum dw 32767 ; initialize data Longnum dd 37999 Singlenum dd 123.45 Doublenum dq 1234.14159 .CODE 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 REFERENCEP ===================================== 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 .MODEL MEDIUM, Basic .CODE pstring PROC sseg:WORD, soff:WORD push bx ; save bx register and dx push dx push es mov ax, sseg ; get segment of string mov es, ax ; put into segment register ; get offset of string mov bx, soff ; 65 = ASCII 'A' mov BYTE PTR es:[bx], 65 ; move the 'A' to the first character ; in the string pop es pop dx pop bx ; restore dx and bx 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 EXTRN BasicSub:PROC .DATA astr DB 'Assembly String ' .CODE 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 EXTRN BasicSub:PROC .DATA BasicRec usrdefType <> .CODE 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: or BYTE PTR es:[bx], 80h ; set sign bit 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 push di push si 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 si pop di 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 needs to be uppercased. 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 ========================================================== 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) CONST Size% = 3% 'Size of the array (# of elements) CONST StrSize% = 11% 'Size of strings stored in array CLS DIM inArray(1 TO Size%) AS STRING * StrSize% DIM outArray(1 TO Size%) AS STRING * StrSize% '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 push si 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 si 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 push si push di 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 di pop si 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 push si push di 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 di pop si 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 push di inc word ptr dgroup:a ; add 1 to a inc word ptr dgroup:c ; add 1 to b mov cx, 101 ; value to initialize 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 di 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 ax, shortnum ; value is stored in 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 ax, WORD PTR longnum ; high order portion in AX mov dx, WORD PTR longnum+2 ; low order portion in DX 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, 4 rep movsw 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 push ds pop es mov di, offset dgroup:str ;load the offset into DI mov ax, 'a' ; load character to fill rep stosb ;store "a" into the string mov cx, [bx] mov bx, offset dgroup:mystring ;put offset of descriptor in ; BX mov [bx], cx ;length in first two bytes mov [bx+2], offset dgroup:str ;offset into second two bytes move ax, bx ;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 'Successfully 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
Additional query words: QuickBas BasicCom
Keywords: kbcode KB71275