Written by Alan O'Hagan (CGI Joe) Dec 9th 2000


Using Interrupts in QuickBasic

Interrupts are an invaluable resource in QuickBasic programs. They give potential access to XMS, EMS, file routines, MSCDEX and many others. While this document wont tell you how to write an EMS handler, it will show you how you go about tackling the problem from within QuickBasic. I will first explain what interrupts are and then will proceed to write a small BMP image loader.

You can think of an interrupt as a SUB or FUNCTION. You Call them with an amount of parameters, they perform some operation and may return a result. The name "Interrupt" simply comes from the fact that you are interrupting the CPU's current task and requesting that it do something else. There are a maximum of 256 (&H0 to &HFF hex) different interrupts and most aren't actually used. But each interrupt may have several sub-functions. For example the main DOS Interrupt is &H21. Through this one interrupt you can print characters, open files, get a directory listing and get system info. You can specify what sub-function you want performed by passing a sub-function number in the AX register. Which brings me to the next topic ..

Passing Data and the CPU

We need a way to communicate with these interrupt functions as we may want to pass them data to manipulate. Here's where we use the CPU. The CPU has a number of Registers which are the same size as an INTEGER in QuickBasic. What we do is, give all the data to the CPU and tell the CPU to call the Interrupt, and that's it! Actually, it's a lot easier than it sounds as we don't have to even touch the CPU directly. We use a TYPE structure whose fields represent the CPU's registers and use a supplied QuickBasic function InterruptX to do all the dirty work.

Here's the TYPE structure: TYPE RegTypeX ax AS INTEGER bx AS INTEGER cx AS INTEGER dx AS INTEGER bp AS INTEGER si AS INTEGER di AS INTEGER flags AS INTEGER ds AS INTEGER es AS INTEGER END TYPE This TYPE structure and declarations for InterruptX can be found in the QB.BI file. So make sure you '$INCLUDE that at the top of your program.

Let me explain what each of those weird symbols represents. Another thing that needs mentioning is that some of these registers can be split into two. When you split an integer in two, you get 2 bytes; a High byte and a Low byte. The registers capable of this are: AX, BX, CX and DX. When split into two, As an example here's how AH and AL combine to make AX:

AX Register | | AH AL 00000000 00000000

The 0's just represent the number of binary digits in each register. A byte (or half an integer) contains eight binary digits (or Bits). AH and AL each have one byte (eight Bits) and AX takes AH and AL to use 16-Bits (an Integer). In Hexadecimal only 2 Digits are needed to represent a byte. So if you change AH or AL, you're changing AX too. Unfortunately, the RegTypeX structure doesn't give us direct access to the smaller High byte and Low byte registers, so we have to do it the long-way-around and use the bigger register to set the smaller one. For example, if we wanted to set AH to 5 in our inregs structure we would do the following:

inregs.ax = &H0500

An example

So what are the steps needed to call an interrupt? Well, what we do is, DIM two copies of this RegTypeX structure: one for data going in and the other for data coming out. These will be called inregs and outregs respectively. Before we call an interrupt, we fill in the required fields in our inregs structure. If the interrupt is supposed to return any data, it will enter it into the outregs structure internally. This can then be checked by you upon exit of the interrupt. As a quick example, here's how you would get the DOS version number using an interrupt: '$INCLUDE: 'qb.bi' DIM inregs AS RegTypeX ' Stuff going in DIM outregs AS RegTypeX ' Stuff coming out ' To get the DOS version, we use (obviously) the DOS interrupt and sub-function &H30 inregs.ax = &H3000 CALL InterruptX(&H21, inregs, outregs) ' Returns Major version in AL, minor in AH maj$ = RTRIM$(STR$(outregs.ax AND 255)) min$ = LTRIM$(STR$(outregs.ax \ 256)) PRINT "DOS Version:"; maj$ + "." + min$ If you want to test this program, you will have to start QuickBasic with the /l switch. This loads in the default QB.QLB library which contains the InterruptX function as well as others.

So hopefully now you have at least some understanding of interrupts. One thing that confused me when I was learning this is, how do I find out which interrupts and sub-function numbers do what? Well, there are references on the internet, specifically Ralf Brown's Interrupt List. Also available are offline references such as HelpPC which contains many things including detailed interrupt listings.

The BMP loader

Yes, I mentioned I was going to take you through the steps of writing something like this, so here it is =). You will find this capable of loading a BMP in less than a tenth of a second, even on a 486! (Yes, some people still use them, including me :P).

The reason we're going to use interrupts (specifically the DOS interrupt, &H21) is because QuickBasic doesn't provide the feature we need to load a BMP quickly enough. What is this feature? It's the ability to copy a series of bytes from a file to a location in memory. While QuickBasic does do this, it's not as flexible as we'd like. Now because we're using DOS file access routines, we'll need to use DOS to Open and Close the file too. Here's the entire source needed to load in a bitmap of any size in 256 colours: '$INCLUDE: 'qb.bi' TYPE BMPHeaderType id AS STRING * 2 'Should be "BM" size AS LONG 'Size of the data rr1 AS INTEGER ' rr2 AS INTEGER ' offset AS LONG 'Position of start of pixel data horz AS LONG ' wid AS LONG 'Image width hei AS LONG 'Image height planes AS INTEGER ' bpp AS INTEGER 'Should read 8 for a 256 colour image pakbyte AS LONG ' imagebytes AS LONG 'Width*Height xres AS LONG ' yres AS LONG ' colch AS LONG ' ic AS LONG ' pal AS STRING * 1024 'Stored as <Blue, Green, Red, 0> END TYPE DIM inregs AS RegTypeX ' Data IN DIM outregs AS RegTypeX ' Data OUT DIM FileHandle AS INTEGER ' Handle used to reference the BMP file DIM BMPHeader AS BmpHeaderType ' The BMP header file$ = "demo.bmp" + CHR$(0) ' BMP file. DOS requires us to append a CHR$(0). inregs.AX = &H3D00 ' Open the File inregs.DS = VARSEG(file$) ' Segment address of the string inregs.DX = SADD(file$) ' Offset address of the string (we use SADD ' when working with variable length strings) CALL InterruptX(&H21, inregs, outregs) IF outregs.FLAGS AND 1 THEN ' Check it opened ok PRINT "Error opening >> " + file$ BEEP: END END IF FileHandle = outregs.AX ' Save the file handle inregs.AX = &H3F00 ' Read in the header inregs.BX = FileHandle inregs.CX = 1078 inregs.DS = VARSEG(BMPHeader) inregs.DX = VARPTR(BMPHeader) CALL InterruptX(&H21, inregs, outregs) SCREEN 13 ' Set dislay mode a$ = BMPHeader.pal ' Change the palette OUT &H3C8, 0 FOR i% = 1 TO 1024 STEP 4 b% = ASC(MID$(a$, i%, 1)) \ 4 'blue g% = ASC(MID$(a$, i% + 1, 1)) \ 4 'green r% = ASC(MID$(a$, i% + 2, 1)) \ 4 'red OUT &H3C9, r% OUT &H3C9, g% OUT &H3C9, b% NEXT PixelBuffer$ = SPACE$(BMPHeader.wid) iHeight% = BMPHeader.hei - 1 Segment% = &HA000 ' Address of SCREEN 13 memory inregs.AX = &H3F00 inregs.BX = FileHandle inregs.CX = BMPHeader.wid FOR y% = iHeight% TO 0 STEP -1 Offset% = VAL("&H" + HEX$(320& * y%)) ' Hack to get integers bigger than 32767 inregs.DS = Segment% inregs.DX = Offset% CALL InterruptX(&H21, inregs, outregs) NEXT y% inregs.DS = 0 ' Close the file inregs.DX = 0 inregs.AX = &H3E00 inregs.BX = FileHandle CALL InterruptX(&H21, inregs, outregs) SLEEP That code is ready to cut and paste. To compile or run, you'll need to start QuickBasic with the /l switch. Of course, I don't mind what you do with this code, a good idea would be to encapsulate it into a SUB so all you'd need to call is something like LoadBMP "demo.bmp". Have fun!

Questions, comments, requests? Mail me

Written for qbasic.qb45.com tutorials