Issue #3 April 7th, 2001 |
Credits Editor: Writers/Contributors: |
Contents
|
Hello!
Well here we are, issue 3 is upon us, and right on the scheduled release date too! =) Well this month I'm sorry to say that there is no RPG Creation 101 article. I have started it but I believe I can make it better and in order to release this issue on time I didn't finish it. Oh well.
Anyway, what have we got this month? Plenty! There's an article on using EMS which is quite lengthy and in-depth. I've written two articles, one on debugging (it seems like every QB mag has one...) and a newbie article on using the mouse in straight QB. And last but far from the least we have another rant for your reading pleasure! Check 'em all out!
And finally I have a request for webmasters who've read this and have enjoyed this. You've probably noticed on the main page that we've got a button to link with. If you'd be so kind as to support us by adding a like on your site then we'll do the same! Contact me for more info.
-Fling-master, editor of the QB Chronicles
Hi
great qb mag =) it has much good and fun stuff to read. Game reviews are fun to
read, i see u have a game/site review, but maybe u could have one game and one
site review.
i dont have much things to say, but keep up the great work =)
Jonge
BinaryMagic.net
Hey thanks for the comments!
It's great to hear that you're doing a good job =) About the reviews: I'm going
to try to get a game review for next month, since I forgot all about it this
month.
Sources
|
|
These are the QB Chronicles editor's Top 10 downloads. These are superb programs/games which any QB'er should have.
Submit your vote! Do you think that there's something that should be on this list which isn't? Then submit your vote! |
|
A quick note: This article is intended for beginners or people who just want something interesting to read. =) It probably won't be of much use to an experienced programmer.
So what is debugging? It is simply put, the process of getting rid of the different kinds of errors in your program. This, can be either a big pain, or if you're like me, a huge joy! Debugging is a big part of programming and you'd better develop some methods and things to watch for or else! Here are the different types of errors:
Syntax Errors: these are the errors that come up because you've spelt something wrong or used improper BASIC syntax. Say you spelled the PRINT command as PRUNT. QBasic would say "Syntax Error" when you tried to run the program.
Runtime Error: These errors show up during your programs execution. For example if you add two numbers into a regular integer variable and the result is larger than 32767 then you'll get an "Overflow" error.
Logic Error: these are the nasty errors. The don't halt program execution but they produce bad program results. Anyone who has made an RPG engine has most likely had one of these. I know when I was working on my scrolling, first try got the scrolling going the wrong way. It worked but the results were unsatisfactory.
Those are the types of errors. Now you're probably asking "How do I get rid of these?" Syntax errors are easy to get rid of. All you do is fix the statement so its in the right syntax. Runtime and Logic errors are harder to get rid of. Runtime errors at least you know when it happens, right where it is, so you can fix them. The only catch here is if the error is inside a DO-LOOP then QBasic will say the problem is with the do loop. In this case you have to look inside the loop or if possible comment out the DO-LOOP statement and leave the loops body intact then try running it. QBasic should stop at the actual error.. The above will also happen with IF-ELSE statements. So you need to check these very carefully! Execute the code in your head that helps me out a lot.
Logic errors are just plain annoying. These, like I said they don't stop execution, but produce unwanted results. The only thing I can say here is that you must find the code which is doing something wrong and study it, execute it in your head, and then hopefully you'll figure it out! You should also know what kind of things a variable should have in it. Also make sure you have spelt your variables right. That may sound stupid but since QBasic doesn't require that you declare your variables (excluding arrays) you could spell a variable named
loopCounter
as
loopConter
That would be enough to get the wrong values into the right places. I've done this many times. And usually it's the very last thing I look for. Sometimes I get so pissed off that I recode the whole thing! Don't let this happen too you.
Now then we can go on to something else. In QBasic there is a very handy menu at the top of the screen called "Debug". It has all sorts of tools for helping you debug. Go ahead and check it out! There are commands near the top that deal with watches. What are those? They "watch" variables and their current values. Type this program into QBasic:
FOR i=1 TO 15 PRINT "Hello again!" NEXT i
Now go to the Add watch command and type this in: "i = 6". Now a blue bar appears and has that expression. Run the program now. You will see that there is a 0 beside it on the blue bar! That means the expression turned true in your program. There would be a "-1" if it didn't turn true. The "Instant Watch" command lets you see the value of a variable throughout the entire program. To see it use Ctrl+Break during program execution or use Trace On. Watchpoint is the same as as Add Watch but if the expression turns true the program execution stops.
Now the last few commands deal with breakpoints. What is a breakpoint you ask? It's where program execution stops if an error occurs or if you press Ctrl+Break. Toggle Breakpoint will highlight the line where execution stopped. Clear breakpoint will get rid of the highlight. These can be a good thing because they let you see the values of variables at any given line. All you have to do it use the Immediate Windows as described below.
I'm sure you've all noticed the Immediate Window on the bottom of the QBasic editing screen. This not only lets you execute one line statements, but if your program execution is interrupted because of and error or a Ctrl+Break keystroke you can use the PRINT command to display your program variables values. This has helped me a lot. Of course if you use watches then you don't need it for that.
Alright, thats it for my debugging article! I hope I helped you out with your debugging techniques. I also hope a lot of you know what the Debug menu does now. I know when I first started using QB I had no clue what that menu was for because I didn't know how to use it. But now you do. (and so do I =) ).
This article was written by: Fling-master - http://www.qbrpgs.com
Download the related file.
QuickBasic has always had a severe problem when it comes to memory. DOS itself can only address 1 MB of memory in real mode. This is the base (conventional) memory and the upper memory area (UMA). Base memory is the first 640 K of memory. The upper memory area is not actually physical memory. Other devices, such as the video card and BIOS, map their memory to this area.
Well, the QuickBasic IDE eats up memory pretty quick, and so does all your program code. Add a couple of sprites, and before you know it you are seeing "Out of Memory" error messages left and right. Now you've got a problem. You still have to add sound, and you don't even have space for all graphics!
There are a couple of solutions to this problem. The first solution is to work outside the QuickBasic IDE, using the command line to compile and test your program. Not only is this a pain, but this only gives you about 300 K more memory to work with.
Figure 1-1: A typical PC memory map |
The second
solution is to somehow access memory above 1 MB. There
are three basic ways to do this: Use extended memory
(XMS), use expanded memory (EMS), or use protected mode.
Protected mode is extremely fast, but it's a huge pain
and is extremely complicated. XMS and EMS are a bit easy
to use, if a little clunky. Data stored with XMS or EMS
must be swapped (or mapped) in and out of the lower 1 MB
memory region so that your program can access it. On the left is a "memory map" of a typical computer. As you can see, the page frame is located at either segment D000h (if not in use) or segment E000h. There is not actually memory located at the page frame; it is just an area that is addressable by the CPU. Using EMS, however, we can map memory above 1 MB to the page frame, allowing the CPU to directly access it. Well, now you're asking yourself, "How do I use XMS or EMS?" MS-DOS provides two drivers, HIMEM.SYS and EMM386.EXE, which provide access to XMS and EMS, respectively. It is important to note that EMM386 requires the HIMEM driver because it uses the XMS memory provided by HIMEM to simulate EMS memory. |
I have chosen to use EMS memory for a couple of reasons. First of all, EMM386 has a much easier interface that can be accessed using interrupts, which can be done using pure QB code. Secondly, EMS provides a 64K page frame in the UMA (See figure 1 above). This means that you don't have to use a buffer in base memory to swap data in and out of (as XMS requires).
"So, are there any disadvantages to using EMS?" Yes, I'm afraid there are two. EMS is a tad slower than XMS, although it is still very fast. The second disadvantage is that only 386 computers can use the EMM386 driver, because it only works on 80386+ processors. (Note: 286s can still use EMS, however, they require physical expanded memory modules or third-party EMS emulators.)
The technique I am going to describe here is supported by the LIM EMS 4.0 standard. LIM stands for Lotus/Intel/Microsoft, which were the three companies that came up with this standard in 1987 for using EMS.
First of all, expanded memory is divided into 16K segments called logical pages. These pages must be allocated to a handle to be used. EMM386 by default provides 64 handles when running in MS-DOS mode, and 256 handles when running under Windows 95/98. The operating system uses one handle, so that leaves your program with 63 or 255 handles to use.
There are two ways to access the data in the logical pages. The first way is to map the pages to the EMS page frame. The page frame is 64K (4 physical pages), allowing you to map up to four logical pages to it. Any data written to the page frame is actually written to the logical pages mapped to it. This method is useful when playing sounds from EMS, because no base memory is required.
The second way to access the data is to use EMM386 to copy the data directly from expanded memory to a location in base memory. Using this method, you can copy the exact number of bytes you need to any segment and offset. This method is useful when dealing with sprites and data arrays.
Before I go any further, let me
just say that all code in this article will be pure QuickBasic. I
am doing this for a couple of reasons. One, I believe that QB
code is a lot easier to understand than assembly, even if it is
slower. Two, anyone who wants the code is assembly can easily
convert it, but converting assembly to QuickBasic is much more
difficult.
Let me also say that I am not aiming to build a super-fast lightning-speed EMS library. I am explaining how EMS works and am providing some solid examples. If you want an EMS library already made and don't care how it works, then you should use DirectQB or the Future Library. That said, let's get started!
Note: All the code in this article, including the sample program, can be found in the ems1.zip file.
EMS is controlled through interrupt 67h. The nice thing about this interrupt is that nearly every service returns a status code in AH after the interrupt returns. This makes it easy to write an error-reporting routine; all we have to do is save the value of AH in a shared variable after each interrupt. We can check if an error occurred by just checking the variable at any time. We can also make a function which returns an error description based on the error code, so we don't have to memorize what all the error codes mean!
Below is the start of our EMS program. In order for the EMS routines to work, you must start QuickBasic with the /L switch. This will load the QB.QLB quicklibrary, which allows us to use interrupts. Notice that we're using integers by default and dynamic arrays. We also need to include the QB.BI file because we're using InterruptX.
DEFINT A-Z
'$DYNAMIC
'$INCLUDE: 'QB.BI'
DIM SHARED Regs AS RegTypeX
DIM SHARED EMS.Error 'Holds the error code of the last operation
DECLARE FUNCTION EMS.ErrorMsg$ ()
FUNCTION EMS.ErrorMsg$
'Returns a text string describing the error code in EMS.Error.
SELECT CASE EMS.Error
CASE &H0: Msg$ = "successful"
CASE &H80: Msg$ = "internal error"
CASE &H81: Msg$ = "hardware malfunction"
CASE &H82: Msg$ = "busy -- retry later"
CASE &H83: Msg$ = "invalid handle"
CASE &H84: Msg$ = "undefined function requested by application"
CASE &H85: Msg$ = "no more handles available"
CASE &H86: Msg$ = "error in save or restore of mapping context"
CASE &H87: Msg$ = "insufficient memory pages in system"
CASE &H88: Msg$ = "insufficient memory pages available"
CASE &H89: Msg$ = "zero pages requested"
CASE &H8A: Msg$ = "invalid logical page number encountered"
CASE &H8B: Msg$ = "invalid physical page number encountered"
CASE &H8C: Msg$ = "page-mapping hardware state save area is full"
CASE &H8D: Msg$ = "save of mapping context failed"
CASE &H8E: Msg$ = "restore of mapping context failed"
CASE &H8F: Msg$ = "undefined subfunction"
CASE &H90: Msg$ = "undefined attribute type"
CASE &H91: Msg$ = "feature not supported"
CASE &H92: Msg$ = "successful, but a portion of the source region has been overwritten"
CASE &H93: Msg$ = "length of source or destination region exceeds length of region allocated to either source or destination handle"
CASE &H94: Msg$ = "conventional and expanded memory regions overlap"
CASE &H95: Msg$ = "offset within logical page exceeds size of logical page"
CASE &H96: Msg$ = "region length exceeds 1 MB"
CASE &H97: Msg$ = "source and destination EMS regions have same handle and overlap"
CASE &H98: Msg$ = "memory source or destination type undefined"
CASE &H9A: Msg$ = "specified alternate map register or DMA register set not supported"
CASE &H9B: Msg$ = "all alternate map register or DMA register sets currently allocated"
CASE &H9C: Msg$ = "alternate map register or DMA register sets not supported"
CASE &H9D: Msg$ = "undefined or unallocated alternate map register or DMA register set"
CASE &H9E: Msg$ = "dedicated DMA channels not supported"
CASE &H9F: Msg$ = "specified dedicated DMA channel not supported"
CASE &HA0: Msg$ = "no such handle name"
CASE &HA1: Msg$ = "a handle found had no name, or duplicate handle name"
CASE &HA2: Msg$ = "attempted to wrap around 1M conventional address space"
CASE &HA3: Msg$ = "source array corrupted"
CASE &HA4: Msg$ = "operating system denied access"
CASE ELSE: Msg$ = HEX$(EMS.Error) '"undefined error"
END SELECT
EMS.ErrorMsg$ = Msg$
END FUNCTION
Now that we have an error checking routine, it will be easier to diagnose any problems that we may have when creating our EMS routines or using them in a program.
Before we start using EMS, our
program should first check and see whether an expanded memory
manager (EMM) is installed. This is accomplished by getting the
interrupt vector address for interrupt 67h, which is the EMM
interrupt. DOS provides an interrupt routine to return this
information:
Interrupt 21h Service 35h - Get Interrupt Vector |
|
AH = | 35h |
AL = | Interrupt number |
After INT 21h Returns: |
|
ES = | Segment of ISR |
Once we have
the segment of interrupt 67h, we can check to see if an EMM is
installed. This is done by changing the segment to ES and looking
at the memory location at offset Ah (10). If an EMM is installed,
we should find "EMMXXXX0" here. Below is a function
that will do this:
DECLARE FUNCTION EMS.Init ()
FUNCTION EMS.Init
'Returns true (-1) if an EMM is installed
'or false (0) if an EMM is not installed.
Regs.ax = &H3567 'Get the interrupt vector for int 67h
InterruptX &H21, Regs, Regs
DEF SEG = Regs.es 'Point to the interrupt segment
FOR x = 10 to 17 'Store the 8 bytes at ES:0A in EMM$
EMM$ = EMM$ + CHR$(PEEK(x))
NEXT
IF EMM$ <> "EMMXXXX0" THEN
EMS.Init = 0 'EMM not installed
ELSE
EMS.Init = -1 'EMM installed
END IF
END FUNCTION
We now have a simple function that
tells us whether or not an EMM is installed. It would be nice to
know what version is installed, because if it is below 4.0, some
of our routines won't work! Interrupt 67h Service 46h does this:
Interrupt 67h Service 46h - Get EMM Version |
|
AH = | 46h |
After INT 67h Returns: |
|
AH = | Status |
AL = | EMM version number, with the major version stored in the 4 MSB and the minor version stored in the 4 LSB. |
DECLARE FUNCTION EMS.Version$ ()
FUNCTION EMS.Version$
'Returns a string containing the EMM version.
'(Must be "4.0" or greater to use our routines.)
Regs.ax = &H4600 'Get the EMM version
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Save the status code
Version = Regs.ax AND &HFF 'Split the version number into
Major = (Version AND &HF0) \ &H10 'its major and minor counterparts
Minor = Version AND &HF
EMS.Version$ = LTRIM$(STR$(Major)) + "." + LTRIM$(STR$(Minor))
END FUNCTION
Also, before we allocate any
expanded memory, it would be nice to know where the page frame
is. This way, we know where to find the pages that we map to the
page frame. Service 41h does this.
Interrupt 67h Service 41h - Get Page Frame Segment |
|
AH = | 41h |
After INT 67h Returns: |
|
AH = | Status |
BX = | Segment of page frame |
DECLARE FUNCTION EMS.PageFrame ()
FUNCTION EMS.PageFrame
'Returns the segment of the EMS page frame
Regs.ax = &H4100 'Get the segment of the page frame
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Save the status code
EMS.PageFrame = Regs.bx
END FUNCTION
Well, we're still not ready to allocate any memory. We need to check and make sure that the EMM has enough handles. The number of available (remaining) EMS handles can be found by subtracting service 4Bh (handles in use) from service 54h subfunction 02h (total handles)
Interrupt 67h Service 4Bh - Get Number of EMM Handles |
|
AH = | 4Bh |
After INT 67h Returns: |
|
AH = | Status |
BX = | Number of EMM handles in use |
Interrupt 67h Service 54h Subfunction 02h - Get Total Number of Handles |
|
AH = | 54h |
AL = | 02h |
After INT 67h Returns: |
|
AH = | Status |
BX = | Total number of EMM handles |
DECLARE FUNCTION EMS.FreeHandles ()
FUNCTION EMS.FreeHandles
'Returns the number of free (available) EMS handles.
Regs.ax = &H4B00 'Get the # of handles in use
InterruptX &H67, Regs, Regs
UsedHandles = Regs.bx
Regs.ax = &H5402 'Get the total # of handles
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
TotalHandles = Regs.bx
EMS.FreeHandles = TotalHandles - UsedHandles 'Subtract to get the # of free handles
END FUNCTION
As stated previously, the number of free handles will usually be 63 when running in pure DOS or DOS mode, or 255 when running under Windows 95/98.
We're almost ready to allocate some memory! We just need to know one more thing: how much memory is available to allocate?! Service 42h returns both the total number of EMS logical pages and the free number of EMS logical pages. Since a page is 16K, we can find the amount of memory for each in KB by multiplying by 16.
Note: It is possible to
allocate all available pages when running in pure DOS or DOS
mode. When running under Windows 95/98, however, you need to
leave 40 pages (640 KB) free.
Interrupt 67h Service 42h - Get Number of Pages |
|
AH = | 42h |
After INT 67h Returns: |
|
AH = | Status |
BX = | Number of free EMS pages |
DX = | Total number of EMS pages |
DECLARE FUNCTION EMS.FreePages ()
DECLARE FUNCTION EMS.TotalPages ()
FUNCTION EMS.FreePages
'Returns the number of free (available) EMS pages
'(Multiply by 16 to get the amount free EMS in KB.)
Regs.ax = &H4200 'Get the # of free pages
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
EMS.FreePages = Regs.bx
END FUNCTION
FUNCTION EMS.TotalPages
'Returns the total number of EMS pages
'(Multiply by 16 to get the total amount of EMS in KB.)
Regs.ax = &H4200 'Get the # of total pages
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
EMS.TotalPages = Regs.dx
END FUNCTION
Finally! We're ready to allocate some pages to a handle! Service 43h allocates the number of pages in BX and returns the EMS handle the memory is allocated to. You must save the value of this handle! You will need it when using other EMS functions, similar to the way you use file numbers in QuickBasic.
Interrupt 67h Service 43h - Get Handle and Allocate Memory |
|
AH = | 43h |
BX= | Number of logical pages to allocate |
After INT 67h Returns: |
|
AH = | Status |
DX = | Handle |
DECLARE FUNCTION EMS.AllocPages (NumPages)
FUNCTION EMS.AllocPages (NumPages)
'Allocates the number of pages in [NumPages] and
'returns the EMS handle the memory is allocated to.
Regs.ax = &H4300 'Allocate [NumPages] pages of EMS
Regs.bx = NumPages
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
EMS.AllocPages = Regs.dx 'Return the handle
END FUNCTION
Figure 1-2: 8 pages allocated to handle #1, and 10 pages allocated to handle #2 |
Every time you call this function, the
memory is allocated with a different handle. The pages in
each handle are numbered starting with 0. On the left is
a visual representation of 8 pages allocated to handle #1
and 10 pages allocated to handle #2. You will notice that there are two pages numbered 0. These are different pages! One page 0 is allocated to handle #1, and the other page 0 is allocated to handle #2! That is why it is important to specify the correct handle when using EMS, otherwise EMM386 won't know which page you mean. One very important thing to remember when allocating EMS is that you must deallocate the memory at the end of your program! If you don't, the memory will be "lost" until your computer is restarted. Service 45h releases the pages allocated to a handle, as well as freeing the handle for later use. Note: If you're running under Windows, you can get the memory back by closing the DOS box and opening another one. You should still deallocate the memory, however. |
Interrupt 67h Service 45h - Release Handle and Memory |
|
AH = | 45h |
DX= | EMM Handle |
After INT 67h Returns: |
|
AH = | Status |
DECLARE SUB EMS.DeallocPages (Handle)
SUB EMS.DeallocPages (Handle)
'Deallocates the EMS pages allocated the EMS handle [Handle].
'You MUST remember to call the sub before your program ends
'if you allocate any memory!
Regs.ax = &H4500 'Release the pages allocated to [Handle]
Regs.dx = Handle
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
END SUB
Now that we have the ability to allocate memory to a handle, we should be able to do something with it! As I said before, there are two ways to access EMS: mapping up to 4 pages to the page frame, and working with the data on the pages from there, or copying the data from EMS to a specified location base memory.
I'll start with the page frame approach first, since it's the simpler of the two. Service 44h maps a single logical page in EMS to one of four physical pages in the page frame.
Interrupt 67h Service 44h - Map Memory |
|
AH = | 44h |
AL = | Physical page number (0-3) |
BX = | Logical page number |
DX = | Handle |
After INT 67h Returns: |
|
AH = | Status |
DECLARE SUB EMS.MapPage (Physical, Logical, Handle)
SUB EMS.MapPage (Physical, Logical, Handle)
'Maps the logical EMS page [Logical] (allocated to the handle [Handle])
'to the physical page [Physical] in the EMS page frame.
Regs.ax = &H4400 + Physical 'Map the logical page [Logical]
Regs.bx = Logical 'to the physical page [Physical]
Regs.dx = Handle
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
END SUB
If we want to map more than one page, service 50h will do this, but it's a bit more complicated. We must first set up a buffer with a list of logical pages and the physical pages to map them to. We then pass the segment and offset of this buffer to the interrupt.
Interrupt 67h Service 50h Buffer Format |
|||
Offset | Length | Type | Description |
0 | 2 | INT | Logical page number |
2 | 2 | INT | Physical page to map to logical page |
Buffer continues with entries alternating logical/physical for up to 4 pages. Size of buffer will range from 4 bytes (1 page) to 16 bytes (4 pages). |
Interrupt 67h Service 50h - Map Multiple Pages |
|
AX = | 5000h |
CX = | Number of pages to map |
DX = | Handle |
DS = | Segment of buffer described above |
SI = | Offset of buffer described above |
After INT 67h Returns: |
|
AH = | Status |
DECLARE SUB EMS.MapXPages (PhysicalStart, LogicalStart, NumPages, Handle)
SUB EMS.MapXPages (PhysicalStart, LogicalStart, NumPages, Handle)
'Maps up to 4 logical EMS pages to physical pages in the page frame, where:
'
'PhysicalStart = Physical page first logical page is mapped to
'LogicalStart = First logical page to map
'NumPages = Number of pages to map (1 to 4)
'Handle = EMS handle logical pages are allocated to
'Create a buffer containing the page information
FOR x = 0 to NumPages - 1
MapInfo$ = MapInfo$ + MKI$(LogicalStart + x) + MKI$(PhysicalStart + x)
NEXT
Regs.ax = &H5000 'Map the pages in the buffer
Regs.cx = NumPages 'to the pageframe
Regs.dx = Handle
Regs.ds = VARSEG(MapInfo$)
Regs.si = SADD(MapInfo$)
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
END SUB
The LIM 4.0 standard introduced another way to access EMS besides the page frame: direct copying! Service 57h copies memory from any location in EMS or base memory to any location in EMS or base memory. (Thanks to zChip for pointing this out on the NeoBasic board.)
This allows four kinds of copying: EMS to EMS, EMS to base, base to EMS, and even base to base! This service also requires a buffer so it knows exactly how to copy the memory.
Interrupt 67h Service 57h Buffer Format |
|||
Offset | Length | Type | Description |
0 | 4 | LONG | Length of memory to copy |
4 | 1 | BYTE | Source memory type: CHR$(0) for base, CHR$(1) for EMS |
5 | 2 | INT | Source memory handle (Use 0 if source is base memory) |
7 | 2 | INT | Source memory offset |
9 | 2 | INT | Source memory segment (Use page number if source is EMS) |
11 | 1 | BYTE | Destination memory type: CHR$(0) for base, CHR$(1) for EMS |
12 | 2 | INT | Destination memory handle (Use 0 if destination is base memory) |
14 | 2 | INT | Destination memory offset |
16 | 2 | INT | Destination memory segment (Use page number if destination is EMS) |
Size of buffer will always be 18 bytes. |
Interrupt 67h Service 57h - Copy Memory Region |
|
AX = | 5700h |
DS = | Segment of buffer described above |
SI = | Offset of buffer described above |
After INT 67h Returns: |
|
AH = | Status |
DECLARE SUB EMS.CopyMem (Length&, SrcHandle, SrcSegment, SrcOffset, DstHandle, DstSegment, DstOffset)
SUB EMS.CopyMem (Length&, SrcHandle, SrcSegment, SrcOffset, DstHandle, DstSegment, DstOffset)
'Copies memory from EMS or base memory to EMS or base memory, where:
'
'Length& = Length of memory to copy in bytes
'SrcHandle = EMS handle of source memory (use 0 if source is base memory)
'SrcSegment = Segment of source memory (or page number if source is EMS)
'SrcOffset = Offset of source memory
'DstHandle = EMS handle of destination memory (use 0 if destination is base memory)
'DstSegment = Segment of destination memory (or page number if destination is EMS)
'DstOffset = Offset of destination memory
'Determine the source and destination memory types by checking the handles
IF SrcHandle = 0 THEN SrcType$ = CHR$(0) ELSE SrcType$ = CHR$(1)
IF DstHandle = 0 THEN DstType$ = CHR$(0) ELSE DstTYpe$ = CHR$(1)
'Create a buffer containing the copy information
CopyInfo$ = MKL$(Length&) + SrcType$ + MKI$(SrcHandle) + MKI$(SrcOffset) + MKI$(SrcSegment) + DstType$ + MKI$(DstHandle) + MKI$(DstOffset) + MKI$(DstSegment)
Regs.ax = &H5700 'Copy the memory region
Regs.ds = VARSEG(CopyInfo$) 'described in the buffer
Regs.si = SADD(CopyInfo$)
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
END SUB
Service 57h subfunction 01h is another useful service. It works almost exactly like service 57h, except it exchanges the memory instead of copying it. The format for the buffer is the same.
Interrupt 67h Service 57h Subfunction 01h - Exhange Memory Region |
|
AH = | 57h |
AL = | 01h |
DS = | Segment of buffer described above |
SI = | Offset of buffer described above |
After INT 67h Returns: |
|
AH = | Status |
DECLARE SUB EMS.ExchMem (Length&, SrcHandle, SrcSegment, SrcOffset, DstHandle, DstSegment, DstOffset)
SUB EMS.ExchMem (Length&, SrcHandle, SrcSegment, SrcOffset, DstHandle, DstSegment, DstOffset)
'Exhanges memory from EMS or base memory to EMS or base memory, where:
'
'Length& = Length of memory to exchange in bytes
'SrcHandle = EMS handle of source memory (use 0 if source is base memory)
'SrcSegment = Segment of source memory (or page number if source is EMS)
'SrcOffset = Offset of source memory
'DstHandle = EMS handle of destination memory (use 0 if destination is base memory)
'DstSegment = Segment of destination memory (or page number if destination is EMS)
'DstOffset = Offset of destination memory
'Determine the source and destination memory types by checking the handles
IF SrcHandle = 0 THEN SrcType$ = CHR$(0) ELSE SrcType$ = CHR$(1)
IF DstHandle = 0 THEN DstType$ = CHR$(0) ELSE DstTYpe$ = CHR$(1)
'Create a buffer containing the copy information
ExchInfo$ = MKL$(Length&) + SrcType$ + MKI$(SrcHandle) + MKI$(SrcOffset) + MKI$(SrcSegment) + DstType$ + MKI$(DstHandle) + MKI$(DstOffset) + MKI$(DstSegment)
Regs.ax = &H5701 'Exchange the memory region
Regs.ds = VARSEG(ExchInfo$) 'described in the buffer
Regs.si = SADD(ExchInfo$)
InterruptX &H67, Regs, Regs
EMS.Error = (Regs.ax AND &HFF00&) \ &H100 'Store the status code
END SUB
Well, we now have all the routines needed to fully utilize EMS in our programs! I know that you're itching to see EMS in action, so below is a sample program that tests all of our EMS routines, as well as timing the page map speed and EMS copy speed.
CLS IF NOT EMS.Init THEN PRINT "No EMM detected." END END IF COLOR 14, 1 PRINT SPACE$(22); "Using EMS in QuickBasic: Part 1 of 3"; SPACE$(22) COLOR 15, 0 PRINT STRING$(31, 196); " EMS Information "; STRING$(32, 196) COLOR 7 PRINT "EMM Version: "; EMS.Version$ IF EMS.Version$ < "4.0" THEN PRINT PRINT "EMM 4.0 or later must be present to use some of the EMS functions." END END IF PRINT "Page frame at: "; HEX$(EMS.PageFrame); "h" PRINT "Free handles:"; EMS.FreeHandles IF EMS.FreeHandles = 0 THEN PRINT PRINT "You need at least one free handle to run this demo." END END IF PRINT "Total EMS:"; EMS.TotalPages; "pages /"; EMS.TotalPages * 16&; "KB /"; EMS.TotalPages \ 64; "MB" PRINT "Free EMS:"; EMS.FreePages; "pages /"; EMS.FreePages * 16&; "KB /"; EMS.FreePages \ 64; "MB" IF EMS.FreePages < 64 THEN PRINT PRINT "You need at least 64 pages (1 MB) free EMS to run this demo." END END IF PRINT COLOR 15, 0 PRINT STRING$(31, 196); " Allocation Test "; STRING$(32, 196) COLOR 7 PRINT "Allocating 64 pages (1 MB) of EMS..."; Handle = EMS.AllocPages(64) IF EMS.Error THEN PRINT "error!" PRINT EMS.ErrorMsg$ END ELSE PRINT "ok!" END IF PRINT "Pages allocated to handle"; Handle PRINT COLOR 15, 0 PRINT STRING$(30, 196); " Page Map/Copy Test "; STRING$(30, 196) COLOR 7 PRINT "Mapping logical page 0 to physical page 0..."; EMS.MapPage 0, 0, Handle IF EMS.Error THEN PRINT "error!" PRINT EMS.ErrorMsg$ END ELSE PRINT "ok!" END IF PRINT "Mapping logical pages 0-3 to physical pages 0-3..."; EMS.MapXPages 0, 0, 4, Handle IF EMS.Error THEN PRINT "error!" PRINT EMS.ErrorMsg$ END ELSE PRINT "ok!" END IF PRINT "Copying logical pages 0-31 to logical pages 32-63..."; EMS.CopyMem 512288, Handle, 0, 0, Handle, 32, 0 IF EMS.Error THEN PRINT "error!" PRINT EMS.ErrorMsg$ END ELSE PRINT "ok!" END IF PRINT "Exchanging logical pages 0-31 with logical pages 32-63..."; EMS.ExchMem 512288, Handle, 0, 0, Handle, 32, 0 IF EMS.Error THEN PRINT "error!" PRINT EMS.ErrorMsg$ END ELSE PRINT "ok!" END IF PRINT COLOR 15, 0 PRINT STRING$(22, 196); " 10-Second Speed Test (Please Wait) "; STRING$(22, 196) COLOR 7 Mapped& = 0 StartTime! = TIMER DO UNTIL StartTime! + 5 <= TIMER EMS.MapXPages 0, 0, 4, Handle Mapped& = Mapped& + 4 LOOP Copied& = 0 StartTime! = TIMER DO UNTIL StartTime! + 5 <= TIMER EMS.CopyMem 512288, Handle, 0, 0, Handle, 32, 0 Copied& = Copied& + 1 LOOP PRINT "Pages Mapped/Sec:"; Mapped& \ 5 PRINT "Bytes copied/Sec:"; (Copied& * 512288) \ 5 PRINT COLOR 15, 0 PRINT STRING$(30, 196); " Deallocation Test "; STRING$(31, 196) COLOR 7 PRINT "Deallocating 64 pages..."; EMS.DeallocPages (Handle) IF EMS.Error THEN PRINT "error!" PRINT EMS.ErrorMsg$ END ELSE PRINT "ok!"; END IF KeyPress$ = INPUT$(1) CLS END
Now that we have the fundamentals of using EMS down, we can get to the fun stuff! In part two (coming in issue #4), I'll show you how to use the EMS routines we developed to store multiple sounds and play them back via DMA. In part three, I'll show you how to store sprites and huge data arrays in EMS.
If you have any questions or comments concerning this article, or (gasp!) found an error, please e-mail me.
This article was written by: Plasma357
Advanced math vs. programming
Many people think that you need to be a wizard at math to be a programmer. This is a common misconception, and the QB
community (with people of ages ranging from 12 to 19) is the perfect example to show that the above is a
stereotype. The question: how much math do you actually need to do programming? What about the amount of math needed to program different
levels of programming language? (think QB vs. C++ vs. ASM)
To answer these questions, it is imperative that we look at the programming. Being more experienced in QB programming, I'll talk in terms of QB
programming first.
In simple QB programming, all you need is to know how addition, subtraction, multiplication, division and parenthesizes (in short, brackets) work, and to
take note of the order of precedence of these arithmetic calculations. You don't even have to calculate the numbers. That's because the computer does
all the mental madness itself. You can give it 29644 *12334 +12123 -12422 / 2354, and it spits the correct answer out every time, even taking in account
the performance of the multiplication and division before the performance of addition and subtraction. To calculate the above statement with pencil and
paper would be a rather tedious job, and you should be glad that the computer is there to calculate it. So, in terms of arithmetic, you only need
to know you very basics. A fourth or fifth grade standard in math would do.
A rather basic knowledge of algebra is also needed by the beginner. Your
variables are the algebra symbols in many ways. It should suffice to say that without a background knowledge in the application and usefulness of
algebra, it's going to be almost impossible to accept variables, even in QB programming where many things are simplified for you.
Apart from algebra, a good mathematical knowledge in number types is also recommended. From your math, you should know what is an integer and how it
refuses to take in decimal numbers, preferring instead to round the number off to the nearest whole number (as a side note, please note that in math,
your integers don't have a numerical boundary whereas in QB, your integers
are bound between -32768 to 32767 inclusive. QB, and probably many other programming language's integers are bound like this so that they fit into 2
bytes of space). Know your decimal number system by hard. Since this is QB, you don't really need to know your binary (base 2) and hexadecimal (base 16)
number systems, at least before you reach the advanced portions of programming. You don't need to know the octal (base 8) system either. You
would have to be experienced in hexadecimal number system if you fiddle with ASM though. An understanding in binary is strongly encouraged to understand
the technical stuff such as your data storage and management (for example, how the computer performs arithmetic in binary form quickly). I don't know
how the octal system came about, so I'm not used to base 8 numbers. I don't think any aspiring programmers need to know the octal number system now.
A more abstract part of mathematics is needed in slightly more complex areas of programming in QB: logical operators. Logical operators, in case you do
not know, are your ANDs, ORs, XORs, NOTs and so on; those things that you use in your IF statements (there are more obscure logical operators such as
IMP, EQV, NAND and so on, but nobody uses them, so they are not discussed)
This is not really taught in schools in math lessons. Rather, you should understand this separately as a math concept first, than apply it to your
programming. One way to understand logical operators is to just explain them in English, as in "AND means that both conditions must be true for the
result to be true" or "XOR means that both conditions are true or all conditions are false, the result is false", but a more professional and
standard way to express them is through truth tables. Truth tables are tables plotted for all combinations of the truth of both conditions. In case
you do not know what I am talking about, below are two truth tables, one for AND and one for the obscure XOR, where "0" represents a false condition and
"1" represents a true condition:
As you can see, the illustration of logical operators with the help of truth tables really help.
As for trigonometry, one of my most disliked aspects of mathematics, good news for you: unless you are programming some kind of graph program or some
unimaginable application, you usually won't need to touch trigonometry at all. The same goes with calculus, which is another section of math which I
disliked.
The highest order of mathematics: "logic", cannot be forcefully taught. You can only pick it up through experience. This is the most important part of
programming, and this is the part which most successful programmers are rich in. Logic differs from logical operators because logic is much more
difficult and much broader than logical operators. Logic is the thing in mathematics that allow you to think of proofs in the math field, and
algorithms in the programming field. This applies to QB, and even more in
other lower level programming languages, like C and ASM. Logic is the thing that separates good programs and bad programs. In some ways, logic is our
so-called "common sense", but logic is even more than common sense; logic is common sense used actively.
All in all, remember the crux of this rant: you don't really need advanced math to program, at least in QB. A high school grade is enough. So if you
have a younger friend or brother (or even yourself) who is thinking about programming, go on. There is always room for improvement, and you don't need
many mathematical skills to start programming.
If you wish to read more articles like this, please visit http://qbtalk.zext.net/
This article was written by: Singsong - http://qbtalk.zext.net
Ever seen somebody's paint program? Or the map editor of some RPG in which you were able to move the mouse and easily make your maps without having to use the keyboard? Ever been curious as to how the mouse works? Well If you haven't yet found out then I'm here to show you how to use this wonderful piece of hardware!
The mouse can easily be used from within QB using straight QB! We make use of the the INTERRUPT function and the data structure RegType which allows us to use the computer's registers. You don't have to use assembly for this either! Though it is better to do so in some cases. Anyways...
The mouse has it's own interrupt, 33h (a hexidecimal number so to use it in QB just use &H33). You can control the mouse using the AX register. When the AX register is set to 1, the mouse is turned 'on'. When AX is 2, it is turned 'off'. When it's 3 it'll return information regarding the current status of the mouse such as the number of buttons, current button being pressed and the current mouse position.
Here's a nice chart for you:
Value of AX | Description of what happens |
1 | Mouse is turned on. You can see it on the screen. |
2 | Mouse is turned off. You can no longer see it on the screen. |
3 | Mouse status is returned into
AX, BX, CX and DX. AX = Number of buttons BX = Current button being pressed (1 = left, 2 = right) CX = Current x position on screen * 8 DX = Current y position on screen * 8 |
I hope that helps you out somewhat. =)
Now how do I implement this you ask? Simple. We can use straight QB code for this. No assembly required. (It's quite easy though in assembler.)
DEFINT A-Z '$INCLUDE: 'qb.bi' 'Include the QB.QLB include file CLS DIM regs AS RegType 'This lets us access the registers regs.ax = 1 'Set AX to 1 so we can turn on the mouse INTERRUPT &H33, regs, regs 'Execute interrupt 33h DO 'Start a loop regs.ax = 3 'Set AX to 3 so we can view mouse status INTERRUPT &H33, regs, regs 'Execute the interrupt and get the mouse status 'Now we'll print the mouse status on the screen. Note that we divide CX and DX by 8 to 'the correct column and row position. LOCATE 1, 1 PRINT regs.ax, regs.bx, (regs.cx / 8) + 1, (regs.dx / 8) + 1 LOOP WHILE INKEY$ = "" regs.ax = 2 'When we're done set AX to 2 soe we can turn off the INTERRUPT &H33, regs, regs 'mouse driver. END
That was a quick example that will let you see the mouse in action. It's a continuous loop that constantly prints out the mouse status. Then when the user presses a key the mouse is turned 'off' and the demo ends. Simple. The comments really explain a lot of it as it's not hard stuff.
Now you can use the mouse! What you'll need to do is make routines which check if the mouse is in a certain region of the screen so you can implement push buttons and menus and stuff like that. Maybe next issue I'll write a tutorial on making a mouse driven GUI. Hope this helped!
This article was written by: Fling-master - http://www.qbrpgs.com
Thanks for reading this issue! I hope you enjoyed it. Next issue is due to be released around May the 19th. In it you can expect part 3 of the RPG Creation 101 tutorial, part 2 of the EMS tutorial, and another rant. You might also see a tutorial on making a mouse driven GUI... Stay tuned!
All site content is © Copyright 2001, HyperRealistic Games. This excludes content submitted by other people. |