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.


Originally posted at http://www.doorknobsoft.com/