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
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:
The machine code for:
mov ax, 0013h
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
H$ = CHR$(VAL("&H" + a$))
MID$(program$, I%, 1) = H$
DEF SEG = VARSEG(program$)
Assuming that works, congrats! You just took the long way to doing:
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.
Call Absolute (BYVAL x%, BYVAL y%, BYVAL color%, SADD(program$))
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
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:
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
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:
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:
mov bp, sp
mov ax, [bp+8]
mov cx, [bp+6]
shr ax, cl
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:
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)
DECLARE FUNCTION ShiftRight% (BYVAL num%, BYVAL shifts%)
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 ()
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.