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 pain and is complicated to set up without an extender. Plus it's not really an option for QuickBASIC. 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.
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:
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. |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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.
Now that we have the fundamentals of using EMS down, we can get to the fun stuff! In part two, 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 graphics and huge data arrays in EMS.
If you have any questions or comments concerning this article, or have found an error, please email me.
Author: | Plasma (Jon Ρetrosky) |
Email: | plasma@phatcode.net |
Website: | http://www.phatcode.net/ |
Released: | Apr 7 2001 |