Assembly as the meaning of the word suggests is not what Quick Basic can execute. Assembly is what we want for Quick Basic to execute. Quickbasic can execute machine language only.
If you have an assembly routine, you have to have it translated. Dos' DEBUG can do the job.
Suppose we have this tiny routine:
RETF (Assembly routines always terminate with RETF)
We start DEBUG, type a0, press enter and type all the instructions. Press enter on a blank line to stop. Now type u0> and you will see like a dozen lines of which these are on top:
then you can translate it and build this program to execute it. You read all the bytes as numerical format, convert them to character, add to a string and use the string as instruction space. To do that, you need to know the absolute location of the string. Get the segment by using VARSEG(). To get the beginning of the string, you normally use VARPTR(). You must first note that the beginning of a variable length string is in fact a length identifier. Only 2 bytes after the beginning is the first character in the string. To get the right beginning we use SADD() (string address) instead. This function returns the actual beginning of the string without the length identifier.
asm$ = ""
FOR a = 1 TO 13
asm$ = asm$ + CHR$(h%)
DEF SEG = VARSEG(asm$)
Now, you can rethink the situation and shorten the data line by adding some code. You just store the hex codes in one data string. You can let the program read the string and get every hex code out of it and have it translated to characters. This way you don't have to count how many machine codes you got.
asm$ = ""
FOR a = 1 TO LEN(h$) STEP 2
asm$ = asm$ + CHR$(VAL("&h" + MID$(h$, a, 2)))
DEF SEG = VARSEG(asm$)
I use a simpler way to incorporate assembly into Qbasic.
When I get the code, I store it backwards in MKLs.
will produce the following hex values as reported by DEBUG:
55 89 E5 90 90 90 5D CB
then I take them like this:
55 89 E5 90
90 90 5D CB
I make each group backwards, and stick them in MKL's.
add any code to the end of your code so that the number of bytes is a multiple of 4. This added code will not be executed at all, because retf was executed instead.
so here is the string result:
d$ = mkl$(&H90E58955)+mkl$(&HCB5D9090)
then to call it:
if you have parameters to pass to your routine, you can add them (comma separated) between "call absolute(" and "sadd(d$))".
he also knows the tree, the bear, the moose and the can of beer.
yay canada! *insert maple leaf picture here*
australians say: "GO EAT A TREE!"
in canada, the trees taste like pancakes. MMMM. treecakes. we are all out of french today, but there is some offensive and random stuff in distractions that is in french. (none of it is true.)
Assembly routines (or should I say, machine language?) can read or write Quick Basic variables.
First of all, you need to know the 3 types of variables:
- Value variables are variables where the value is passed to the routine using BYVAL. It can only be read. The routine may overwrite the value, but it is lost once it exits.
- Pointer variables are variables that are identical to Quick Basic variables. The pointer is passed to the routine. Writing a variable by writing a value at the given location in the current segment is analogous to writing a variable in memory using VARPTR() and POKE in Quick Basic.
Variables are stored at the top of the stack before the routine is called. The stack pointer SP is a register which holds the pointer to the top of the stack. To get variables, you must address those locations. SP can not address something so you have to copy the value to another register which can. BP, SI or DI would be a logical choice since all other registers are general-purpose. I recomment you use BP since SI and DI are just too usefull for working with strings, arrays or video memory. Before you start using BP you must backup the value because Quick Basic uses it. Quick Basic also uses DS, SS and SP.
Before you can use variables, you have to know where they are located in the stack. The first 2 bytes if the stack will be the backuped BP value. The next 4 bytes are the return address to where execution resumes after exiting. From the 6th byte and on are 2-byte pointers to all the value and pointer variables passed through.
Now you can make a simple template for assembly supporting variables:
PUSH BP (backup old value)
MOV SP,BP (copy stack pointer)
...your code here
POP BP (restore old value)
To use Value variables, you calculate the address (BP+6 for the first variable) and do this:
to get the first WORD (or the entire variable, if Integer) into AX.
To use Pointer variables, you need to get the address of the variable. You are forced to use a general purpose or reserved register to do this. I would recomment using SI, DI or BX. Watch out for DI because it is by default linked with the ES segment (which by default holds the same as the DS segment), which you can use for your own purposes.
MOV SI,[BP+8] (2nd variable in stack)
MOV [SI],AX (Store the contents of AX into the variable)
Now, the variable is changed in Quick Basic too.
When calling a routine from Quick Basic, the variables passed through are in reversed order. If you have one variable, you write:
CALL absolute(var%, SADD(asm$))
Variable var% is accesssed by [BP+6].
When you add another variable, you put it before var%. This way you dont have to rewrite your code:
CALL absolute(BYVAL color%, var%, SADD(asm$))
Variable var% is still accesssed by [BP+6], and variable color% is accessed by [BP+8], and so on.
In the example above, var% is a pointer variable, which can return a value to Quick Basic, and color% is a value variable because it uses BYVAL.
First of all, I'm curious why the above post was posted under the name MC. Was it a typo for Mac, or are you trying to link it to me? It is Tim's code...
The example code might be simple, but, as an entry in an inefficient code contest, it probably isn't as efficient as it could be. It is using Int 0x21 calls for every character printed rather than just printing one string. Here is a relatively simple example that uses only one Int 0x21 call:
---------------------------------------- nasm source
;assemble in nasm
org 0x0 ;origin for nasm
;we need an absolute near address for the display string function, but we don't know where QBASIC has placed this code.
;this will be encoded with a relative offset (0x0), so will work. It places the return address on the stack. The return address in this case is the same as the destination address, the address of the _next label.
;now we recover that address from the stack. We now know the absolute near address of the _next label.
sub bx,byte _next
;this gives us the absolute near address of the beginning of the code in QBASIC's memory. We will calculate the absolute addresses of other labels by adding this value from the addresses generated by nasm, which are based on an origin of 0x0.
The above method discovers its own location, and is somewhat self contained, not requiring any special effort from the QBASIC program. But maybe you want to pass a value from QBASIC to your assembly code:
---------------------------------------- nasm source
;I am not certain of QBASIC parameter passing conventions, but this seems to work.
mov bx,[bp+0x6] ;get near pointer to descriptor
mov dx,[bx+0x2] ;string offset
---------------------------------------- qbasic implementation
t$ = "Hello, world!" + MKI$(&HA0D) + "$"
c$ = MKL$(&H8BE58955) + MKL$(&H578B065E) + MKL$(&HCD09B402) + MKL$(&H2CA5D21) + CHR$(&H0)
DEF SEG = VARSEG(c$)
CALL absolute(t$, SADD(c$))
Be aware that QBASIC doesn't seem to notice the new cursor position after the call absolute(), so may write over the displayed text.
In the QBASIC implementations, I am using CHR$(), MKI$(), and MKL$() instead of just CHR$(), to save space. If you aren't familiar with MKI$() and MKL$(), they are functions that return strings of data in the same format that numbers are actually stored in. For example the number 0x1234 will be stored in memory as 0x34 0x12. MKI$(&h1234) will return a two byte string with the first byte being 0x34, and the second byte being 0x12. This is the "little-endian" reverse order way that numbers are stored. So if you are trying to read the binary instructions inside the MKI$() and MKL$() functions, be aware that the apparent order of the bytes is reversed from the actual order in memory. For example, the CR+LF (0xd, 0xa) combination is formed as MKI$(&ha0d) above.
This message has been edited by MCalkins on Mar 24, 2008 2:19 AM This message has been edited by MCalkins on Mar 24, 2008 2:10 AM This message has been edited by MCalkins on Mar 24, 2008 12:20 AM