4.  An Example

Back Home Up Next

It should help some to work through an example.

Character reversing

Here's an example QBASIC program which calls an assembly routine in order to reverse the characters in a string.

 DIM i AS INTEGER, count AS INTEGER, ans AS STRING
 RESTORE
 READ count
 REDIM a(1 TO count) AS INTEGER
 FOR i = 1 TO count
     READ a(i)
 NEXT i
 INPUT "Enter a string to be reversed: ", ans
 DEF SEG = VARSEG(a(1))
 CALL ABSOLUTE(LEN(ans), SADD(ans), VARPTR(a(1)))
 PRINT ans
 END
 DATA 20
 DATA &H8B55, &H8BEC, &H085E, &H0F8B, &H5E8B, &H8B06, &H8B1F, &H03FB
 DATA &HEBF9, &H4F0B, &H058A, &H0786, &H0588, &H8343, &H02E9, &HF983
 DATA &H7701, &H5DF0, &H04CA, &H0000      

This is built from the assembly routine mentioned earlier, but here I'm showing the completed program.  This program runs.

Here, you can see the process of reading up the assembly routine into a QBASIC array where it can be accessed by CALL ABSOLUTE.  You can also see the use of VARPTR(), VARSEG(), and DEF SEG as part of making a proper call to an assembly language program.

Finally, you can see an example where two parameters are communicated to the assembly routine, namely the length of the string and the address of its contents, along with the offset address of the subroutine to call.

Let's look at the details a bit.

The DATA

The first data statement simply describes the number of INTEGER values to follow which make up the assembly language subroutine.  In this case, 20.

The rest of the data listed below it are the individual values in the order they must be in so that the instructions are correctly followed, when executed.  If you take the assembly language program presented on the next page, assemble it, and then look at the listing file, you will easily see the correspondance between these values and the listing made by the assembler.  I've also included a program in the parent page which will automatically generate these DATA statements, so that you don't have to manually figure them out and type them in.

The essence here is that the data statements carry the assembly routine in a safe, unusable place in your QBASIC program.  They are unusable in this form because they cannot be directly executed in the form of DATA statements.   Before they can be used, they need to be loaded into an INTEGER array.

The array

The statements, from the RESTORE through the NEXT, are used to create a data array of the right size and then to load the binary values into it needed to place the assembly code into memory where it can be properly accessed.

In order to use the assembly routine in the array, two key QBASIC elements are used.  The first is the DEF SEG, used to set up the segment portion of the address.  You can see where that is set up and how the VARSEG() function refers to the first data item in the array.  The second is the VARPTR() function in the CALL ABSOLUTE, itself.  This specifies the offset portion of the address.  Between both of these elements, you have informed QBASIC of the complete address of your assembly routine.

The reason for using the first element of the array in these cases is because we only have one assembly subroutine present and it starts at the first array element.  If there were several assembly routines inside the array, the offset portion using the VARPTR() function, would need to be changed to reflect the correct offset for the desired routine.  The first routine would be accessed, just as shown above.  But other routines would need a relative offset from it (and you'd need to look at the assembly listing to figure out that relative offset.)

QBASIC does perform garbage collection from time to time, so the arrays can be moved around.  One of the points at which QBASIC might arbitrarily decide to move an array is when you use ERASE or REDIM.  There are others.  What this means to you is that you should place your DEF SEG as close as possible to the CALL ABSOLUTE, to be safe.  If you depend on some long since used DEF SEG or just set it once and forget it, you could get into deep trouble if QBASIC decides it is time to move your array, between the point where the DEF SEG took place and where the CALL ABSOLUTE then takes place.  So be safe and use DEF SEG every time unless you are certain that your array won't be moved.  (There are reasons you might know this, but you have to be pretty experienced in QBASIC.)

SADD()

Strings are special, in QBASIC.  You cannot just use VARPTR() to get the address of the string's contents.  This is because QBASIC uses string descriptors and VARPTR() gets you the address offset of that descriptor and not the contents of the string.  Some folks really do want the offset address of the string descriptor, at times.  In any case, the assembly routine I wrote for this program doesn't know anything about QBASIC string descriptors, so it needs the actual address of the characters in the string and not the address of the descriptor.  (It's not safe to assume much about QB/QBASIC string descriptors, because they actually vary some between different versions of the compiler tools.  For compatibility and maintenence reasons, it is probably better to avoid worrying about descriptors.)

In the unique case of strings, you need to use the SADD() function for the offset address of the character contents of the string.

 

Last updated: Thursday, June 17, 2004 19:28