to Appendix 2
Appendix 1 - ASM in QuickBasic
WARNING! This section of the tutorial is intended for programmers already familiar with QuickBasic/Qbasic, and who have some knowledge of ASM from my tutorial or elsewhere. It will NOT try to teach you QB. That's for a later time, maybe.....
This section assumes you have a copy of QB 4.5 and are using TASM, provided in the ASM Tutorial if you don't have it already.
PART 1 - The CALL
ABSOLUTE way
1.1 - Integrating ASM
There are two methods I'm familiar with for
getting your ASM code into QB. One is by using Call Absolute.
You'll need to have the quick library loaded called qb.qlb. To do
this, find your QB.PIF file and change the part that says
'command line' to say qb.exe /l (that's an L, not a one). Or,
make a batch file that does this. It's beyond the scope of this
text to teach you about batch files; if you're having trouble
loading the library, first find help elsewhere or you can E-mail me.
So, before using call absolute, compile your ASM code, open up a
program like DEBUG and look at the machine code within. Then,
make DATA statements with these. An example would be like so:
DATA B8,13,00,CD,10,CB
The machine code for:
mov ax, 0013h
int 10h
retf
Retf is very important. I beleive procedures through call
absolute are always FAR, but i'm not very sure about it.
From here, you'd load the data into a string and call it. Here's
complete code to effectively call the routine:
program$ = SPACE$(6)
FOR I% = 1 TO 6
READ a$
H$ = CHR$(VAL("&H" + a$))
MID$(program$, I%, 1) = H$
NEXT I%
DEF SEG = VARSEG(program$)
CALL absolute(SADD(program$))
DEF SEG
Assuming that works, congrats! You just took the long way to
doing:
SCREEN 13
Yes, this is a very long and annoying way to go about using an
ASM routine. Soon we'll discuss how to put your code into your
own quick library.
1.2 - Passing Values
Different types of variables all have different
ways for passing them to your ASM code, but mostly it involves
passing the VARPTR to the variable onto the stack. Then, you get
that off the stack, and via indirect addressing can find the
value of the variable. Before getting into the various types of
variables you could pass, let's discuss the simple way: BYVAL.
BYVAL when used in your Call Absolute line gives just the value
of the variable to your routine, rather than the actual address
of it.
Keep in mind that anything passed is placed on the stack. QB
first PUSHes all variables from left to right onto the stack,
then PUSHes CS:IP for return. So, CS:IP will be the top 4 bytes
of the stack.
Lets see a small example of retreiving values. This code,
however, is up to you to turn into machine code and call
We'll do a small pixel drawing routine, using BIOS for simplicity.
In QB:
Call Absolute (BYVAL x%, BYVAL y%, BYVAL
color%, SADD(program$))
In ASM:
push bp
mov bp, sp
mov ax, [bp+6] ; get the color
mov dx, [bp+8] ; get y value
mov cx, [bp+10] ; get x value
mov bx, 0 ; page 0
mov ah, 0ch ; plot pixel function
int 10h
pop bp
RETF 6 ; passed six bytes, now get rid of them and return
In theory that'll work. Haven't tried it, but you can get the
gist of it. So to clarify, the stack is laid out like this when
we call the routine and bp is pushed:
Byte # | Value |
0 | BP |
2 | CS:IP |
6 | color% |
8 | y% |
10 | x% |
This is because first the values are passed left to right, and
anything new on the stack goes on top of the old - First on, Last
off - So x% which is pushed first ends up on the very bottom.
Then CS:IP is pushed, so it ends up on top of your values. And
finally, bp is pushed so it goes on top of CS:IP.
Why go through the trouble of pushing BP and using it for
indirect addressing? Because, only a few registers can be used
for indirect addressing. Two of those are BP and BX. Sp, however,
cannot. You cannot do this:
mov ax, [sp+6]
So instead we preserve bp and put sp into it. Be very careful
however to always pop off the proper number of bytes with your
RETF at the end, and pop bp as well. For this program we pass 6
bytes total, so we must return with RETF 6.
Passing without byval:
As I mentioned different variables are passed
differently, when not passed via BYVAL. The advantage of passing
without BYVAL is that you can know the address of the variable
allowing you to directly change it. Download this file: 3passing (not made by me)
for some example code and info on passing different types of
variables.
PART 2 - The LINKing
way
2.1 - Preparing the code
I think this method for integrating ASM is much easier. You
don't have to convert anything to machine code, type it in in QB,
or even use call absolute. This way, you can call the ASM
routines as if they were normal SUBs/FUNCTIONs. So, how is it
done. It's pretty simple, just a couple steps with your compilers:
First though, we must add a few lines to the asm code. The first
step is to change this:
.MODEL MEDIUM
or whatever model you have, and add ,basic to it allowing it to
be used with basic:
.MODEL MEDIUM, BASIC
Now, anything we want to call from QB must be declared in our ASM
as a PROCedure. Let's assume we have a shifting right routine. It
would look like this:
ShiftRight PROC
push bp
mov bp, sp
mov ax, [bp+8]
mov cx, [bp+6]
shr ax, cl
pop bp
ret 4
ShiftRight ENDP
So to this routine, from QB, we'll pass a number and how many
places to shift. Also notice that unless you specify PROC FAR,
you can just use RET to end the proc.
So the first step is to declare it a proc. Additionally, we need
a line that says it can be used by other programs. So, we'll need
a line making the code PUBLIC:
PUBLIC ShiftRight
Now our code is fully ready to be used in QB
2.2 - Making a Library
A few steps to this. First, run the ASM through
your assembler (TASM.EXE). It will produce a .obj file. Now, move
that file to your QB directory. Either run dos or type it into
the command line in windows explorer:
link /qu mylib qb.lib,asmfile,,bqlb45
Mylib is the output quick library it will make. Asmfile should be
your .obj made by the assembler. I beleive qb.lib and bqlb45
specify libraries to be merged as well, but I could be wrong.
These are merged to allow you to still use call absolute while
using your own library. Now, load QB with QB.EXE /l mylib.qlb
So, how do you use your new routine. Simply declare it and call
it like a normal function (or sub, but in this case, our asm
routine is a function because it returns the shifted number)
Example:
DECLARE FUNCTION ShiftRight% (BYVAL num%,
BYVAL shifts%)
Print ShiftRight(4,1)
That should print 2 on the screen, since 4 shifted right once (which
is basically dividing it by 2) is 2.
There's just a few things that makes an ASM routine called as a
function diffferent from one called as a sub. First, notice that
a % follows the name ShiftRight. This is it's return type, which
in this case is saying that the function returns an integer. So,
how is it returned? With functions, values are returned in AX (sorry,
i don't know about returning 32-bit values. AX for 16 bit, though).
That's why our ASM routine leaves the shifted number in AX, then.
If this were a SUB, you would simply omit the return type, and
you can no longer use it with a print or as part of other such
expressions
DECLARE SUB Set320x200x256 ()
Set320x200x256
This would call a routine setting mode 13h. It takes
nothing and returns nothing, so we just use a sub with no
parameters passed to it (don't take this to mean subs can't take
parameters - they can)
Well, that's the extent of my knowledge on this subject. If you
think there's some info I should add, just E-mail me.