Issue #3
April 7th, 2001

Credits

Editor:
Fling-master

Writers/Contributors:
Plasma357
Singsong

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
  • Future Software MsgBoard
  • QuickBASIC News
Problems with some QB sites...
As most of you have probably noticed already Future Software and QBasicNews have already been kicked off of their old host because they can no longer host any websites. This caused some commotion in the QB community as many people thought that their favourite QB sites were going down never to come back.

Well Future Software's problems are resolved (I think). Jorden has re uploaded the site to the new server located at http://www.site5.com. QBasicNews currently is having problems finding a new host so if you can host them then contact them.


Site redesigns/updates.
Recently two sites have been updated. Firstly VGameSoft redesigned and gave the first update for quite a long time. The design is quite good too. Secondly Statto Software has been redesigned. Darkness Ethereal was redesigned awhile ago too. There's also a new rant up. Congrats to the webmasters on their sites great layouts!

Tetsuyu 3 has been delayed
This game was going to be released last month sometime but since the homepage was re-launched they are pushing for a April-May release.

GBGames updated with more reviews!
Three more reviews were added last month: Arcade Ralley, Super Sumo, and xTron. Check it out at http://www.gbgames.com.

Ghini Run Demo 2
Earlier last month Piptol released another demo of this cool game. Visit the homepage here and download the game!

VPlanet Updated
VPlanet was also updated with some new articles and a review. Visit it at http://vplanet.cjb.net.

Master Creating is active once again!
Master-creating now updates their site! You can go their to get some news and hopefully some new downloads every once in a while of their projects.

Neozones returning?
Recently it was seen that Neozones had a "Returning soon..." message. Is this the return of Neozones? We'll keep you posted!

Master Creating is active once again!
Master-creating now updates their site! You can go their to get some news and hopefully some new downloads every once in a while of their projects.

 

 

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!

Name of program:

Rank Description
1 Future.Library
Probably the best library out there. It can do it all. Great sprite management. XMS and EMS support. Great sound handling. SVGA resolutions. And they are all fast. Get this! Now!!
2 Shadow of Power
One of the best QBRPG's out there. Fantastic graphics, Zelda-like game play, good sound, and a development kit all included.
3 DirectSound for QB
This (obviously) allows you to use DirectSound in your QB programs! One plus for this is that you don't have to include a setup program to setup sound settings. Get it.
4 Zeta BETA 2
A cool SVGA 3D game. Very realistic graphics, and great sound, not to mention superb game play. A big download but worth it!
5 DirectQB
This is still the best VGA library available. (Well second only to Future.Library) It supports EMS layers, lots of graphics, sound, disk, keyboard, etc routines. Try it today!
6 Wetspot 2
Some people might say that I shouldn't bother putting up an old game, but this one is fantastic. Great graphics, multiplayer modes, a level designer. And its FUN!! =)
7 Arrakis
Somewhat old now but one of few strategy games. This ones complete too! Features a RMG, great AI, graphics, sound and game play.
8 Ultimate Super Stack
A way-cool puzzle game with lots of levels. The graphics are really good too.
9 Dynamic
Great space-shooter with good graphics and game play Probably the best space-shooter which is in QB.
10 QMidi v4.1
Ever wanted to play MIDI files in your game? Well this will let you! Get this if you don't use DS4QB.

 

 

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.

Introduction: Some History, a Problem, and a Solution

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.)

Overview: The Basic Concepts of Using EMS

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.

Using EMS: Let's Do It!

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

A Sample Program

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

Until Next Time...

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.