Using EMS in QuickBASIC: Part 1
by Plasma (Jon Ρetrosky)
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 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.)
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$ = "undefined error: " + HEX$(EMS.Error) + "h"
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 |
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, 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.
|