SUMMARY
This article describes memory management using Microsoft Basic
Professional Development System (PDS) version 7.0 and 7.1 for MS-DOS.
The following sections are included:
INTRODUCTION
COMPILED WITH NEAR STRINGS
COMPILED WITH FAR STRINGS
QBX.EXE ENVIRONMENT (FAR STRINGS)
DYNAMIC versus STATIC ARRAYS
HUGE ARRAYS
SIZE LIMITS
HOW TO DETERMINE AVAILABLE MEMORY
WHAT TO DO WHEN YOUR PROGRAM RUNS OUT OF MEMORY
MORE INFORMATION
================
INTRODUCTION
------------
Basic PDS manages memory in three distinct ways:
- Compiled with Near Strings:
Unless otherwise specified, the default memory model for a
compiled program is one using Near string addressing.
- Compiled with Far Strings:
To compile a program using Far string addressing you must use
the /Fs switch. This will allow variable length strings to be
stored in multiple segments in Far memory, that is, outside of
DGroup.
- Interpreted (in the QBX.EXE environment) with Far Strings:
This also uses Far string addressing. In addition it
implements additional data and code storage techniques unique
to operation within the development environment.
Each of these three memory models will be discussed in detail later in
this document. Each model represents a different way in which program
code and data is stored in Random Access Memory (RAM). RAM itself is
also divided into three types of memory (see map at and of section):
- Conventional MS-DOS Memory (0 up to 640K)
This memory is subdivided into three areas:
- Low Memory:
This is where the Interrupt vector table and MS-DOS
code resides. Any TSR programs or device drivers may also
be loaded here. On top of that either program code
or the QBX environment code will be loaded.
- Near Memory (DGroup):
This is a single Data segment used by Basic. It can
be up to 64K in size.
- Far Memory:
This is the area of memory from where DGroup stops to
the end of the conventional MS-DOS Memory.
- 384K Reserved I/O Address Space (Upper Memory Blocks):
This is the area of memory between the top of Conventional
Memory (640K) and the start of Extended Memory (1024K). This
area of memory can contain the following:
- BIOS (basic input/output system), ROM chips, and
display adapter memory.
- Accessory cards such as network cards.
- 64K Page frame for Expanded Memory
- Extended Memory (XMS) driven by EMM386.EXE and HIMEM.SYS:
This is linear memory above 1MB (1024K).
Basic only uses the first segment (64K) of extended memory just
above the 1MB boundary. This memory area is referred to as the
High Memory Area. The QBX environment will load up to 60K of
its code into this segment, that is assuming that no other
program or portion of a program is loaded here (such as MS-DOS
5.0).
- Expanded Memory (EMS) driven by EMM386.EXE:
This memory is available through an installable memory board
in your system. It can also be acquired using the EMM386
memory driver to access Extended Memory as Expanded Memory.
This memory is available to the program as four 16K pages. This
memory can be used by the QBX environment for code and data
storage, and is used by compiled applications only for code
storage (except ISAM) when using overlays provided through the
Linker utility.
GENERAL MEMORY MAP
------------------
=======================================
<---
|
Expanded Memory (EMS) <---|
up to 8MB on a PC |
(not linear memory) <---|
|
<---|
|
======================================= |
|
|
|
16MB ======================================= |
| |
| |
Extended Memory (XMS) |
up to 15 MB on a PC |
| |
| |
| --------------------------------------- |
| * High Memory Area 64K * |
1MB --------------------------------------- |
| |
| ----------------------------- <---|
| ------ EMS Page Frame ------- |
384K Reserved ----------------------------- |
I/O space ------(four 16K pages)------- |
| ----------------------------- <---
|
|
640K ---------------------------------------
.
.
Conventional Memory
.
.
0 =======================================
COMPILED WITH NEAR STRINGS
--------------------------
Using "Near Strings" means that variable length strings are referenced
using near addressing and thus are stored in Near Memory (DGroup).
Near Memory (DGroup) can be up to 64K in size, but after
subtracting the space required for Basic's internal variables and
stack, only about 46K is left as available for your program.
The following is a breakdown of Near Memory (DGroup) and what may be
stored here when you use this memory model. The areas of Near Memory
include: Static Data, the Stack, the Local Heap, and String Space.
Static Data:
Data that is of a fixed size and the memory for which is fixed
(pre allocated) prior to program execution.
- Internal Variables:
Variables used by Basic. They maintain information on the
current video mode; cursor size and visibility status; the
next cursor row and column; MS-DOS version; and more.
- String Literals:
Such as "Hello" in the code: Print "Hello".
- DATA Statements:
All information in a DATA Statement, both string and
numeric.
- Common Data:
All Common data, named and blank common, fixed length and
variable length data.
- Static Arrays:
Arrays with dimensions fixed prior to program execution.
These arrays can not be REDIMed.
- String Constants:
Declared with the CONST statement, such as NAME in the code:
CONST NAME = "BOB"
NOTE: all numeric Constants are stored as code in the code
segment.
- Scalar Variables (Simple Variables):
Non-DIMensioned variables such as X%, Y$, Count#, etc.
- User Defined Type Variables:
Variables DIMensioned by a TYPE...END TYPE structure.
- Fixed Length Strings:
String variables DIMensioned to a specific size,
i.e. DIM Name as String * 10.
Stack:
Temporary storage area for certain types of data, such as local
variables in Sub Procedures. The size by default is 3K. This
can be increased or decreased using the CLEAR statement. For
example: CLEAR,,1024 will set the stack size to 1K (1024 bytes).
Local Heap:
Part of the Near Heap which is a portion of Near Memory (DGroup).
The Local Heap contains:
- Array Descriptors:
All arrays require a descriptor. Single dimension arrays
require an 18-byte descriptor, and multiple dimension
arrays require an additional 4-bytes for each additional
dimension beyond the first. Note that an array of variable
length strings is really and array of 4-byte string
descriptors (see below).
- String Descriptors:
Only variable length strings require a descriptor, fixed
length strings do not. This 4-byte descriptor describes the
string's length and starting location in memory. Note that
each variable length string in an array requires a
descriptor.
- File Data Block Segments:
This is where the file buffer is created to store data. You
specify the size of this buffer with the LEN clause at the
end of an OPEN statement:
OPEN "Test.txt" for Random as #1 Len = 512.
String Space:
Places where variable length string data is stored. In this
memory model it is located in Near Memory (DGroup). In other
models (Far String model) it will be located in Far Memory.
- Variable Length Strings:
Only the string data is stored here, not the string
descriptors, they are stored in the Local Heap. This will
be an important distinction when using the Far String memory
model.
- Dynamic Variable Length Strings Arrays:
As with variable length strings, only the string data is
stored here. The string descriptors and array descriptors
are stored in the Local Heap. Note this applies to only the
Dynamic variable length string arrays. The memory for these
arrays is not allocated until runtime. The arrays can be
REDIMed.
The following is a breakdown of Far Memory and what may be stored
here when you use this memory model. The areas of Far Memory include:
The Far Heap, Runtime Module Code, and the Communications Buffer.
Far Heap:
This is the rest of conventional MS-DOS memory up to the 640K
boundary after the demands of Low Memory, Near Memory (DGroup)
and other components of Far Memory are met.
- Dynamic Fixed Element Arrays:
Again, only the array data is stored here. The array
descriptor is stored in the Local Heap. Dynamic fixed
element arrays are composed of elements with a fixed size,
such as numbers, user defined types, or fixed length
strings. Being Dynamic, these arrays can be REDIMensioned
to different lengths but the size of their elements must
remain fixed. This is where Huge Arrays are stored (see
section on Huge Arrays).
Runtime Module Code:
Programs compiled without the /O switch require a runtime module
(BRUNXX.EXE or BRTXX.EXE). When the program is executed the
runtime module is loaded into memory. If the program is compiled
with the /O switch, as a stand alone application, the runtime
module is not required.
Communications Buffer:
This is an intermediate storage area for data sent or received
from a communications port. The default size is 512K. It can be
adjusted from 0 to 32,767 (32K). This is done with the /C:XXXX
switch on the compiler, the XXXX being the number of bytes to
reserve for the buffer.
COMPILED NEAR STRING MEMORY MODEL
---------------------------------
MS-DOS ---> -------------------------------------- <---
640K Communications Buffer |
Boundary -------------------------------------- |
Runtime Module Code |
-------------------------------------- |
|
Far Memory
Far Heap: |
Dynamic Fixed-Element Arrays |
|
|
|
---> ====================================== <---
| |
| String Space: |
| Variable Length Strings |
| Dynamic Variable Length String Arrays |
| |
| -------------------------------------- Near Heap
| |
| Local Heap: |
| Array & String Descriptors |
| File Data Block Segments |
| |
| -------------------------------------- <---
Data Group Stack
(DGroup)--------------------------------------
Near Memory
| Static Data:
| Internal Variables
| String Literals
| Data Statements
| Common Data
| Static Arrays
| Constants
| Scalar Variables
| User Defined Type Variables
| Fixed Length Strings
|
---> ====================================== <---
|
Program Code |
Low Memory
-------------------------------------- |
MS-DOS |
-------------------------------------- <---
COMPILED WITH FAR STRINGS
-------------------------
Using "Far Strings" means that variable length strings are referenced
using far addressing and thus can be stored in Far Memory.
The following is a break down of Near Memory and what may be stored
here when you use this memory model. It is pretty much the same as
the Near Memory model when using Near Strings except for the
following:
Static Data:
Only fixed length Common Data. (Common data for variable
length strings is stored in Far Memory).
The following is a break down of Far Memory and what may be stored
here when you use this memory model. String Space is now located in
Far memory for variable length strings. It is referred to as the
String Segments. Note, fixed length strings and Static arrays are
still stored with all other Static Data in DGroup as before:
String Segments:
- Local Dynamic String Array Segments:
Data of Dynamic string arrays created within a Sub Procedure
or Function is stored in a segment of this type. A new
segment is allocated for each Sub or Function. The segment
can be up to 64K in size.
- Module Level String and Named Common String Segment
This is an independent segment up to 64K in size. This
segment stores all data from variable length strings and
string arrays created in the main module. It also contains
data from variable length strings and string arrays in Named
Common blocks.
- Blank Common String Segment:
This is another independent segment up to 64K in size. This
segment contains data from variable strings in Blank Common
blocks.
- Local and Temporary String Segment:
This is another independent segment up to 64K in size. This
segment is shared by all procedures and module level code.
In it is stored all data for local and temporary variable
length strings created in a given program.
Far Heap:
The File Data Block is now located here.
COMPILED FAR STRING MEMORY MODEL
--------------------------------
MS-DOS --> -------------------------------------- <---
640K Communications Buffer |
Boundary -------------------------------------- |
Runtime Module Code |
-------------------------------------- |
|
Far Heap: |
Dynamic Fixed-Element Arrays |
File Data Block Segments |
Far Memory
-------------------------------------- |
|
String Segments (Variable Length Strings): |
Local & Temp. String Segment |
Module Level String & |
Named Common String Segment |
Blank Common String Segment |
Local Dynamic String Array Segments |
|
--> ====================================== <---
| |
| Local Heap: Near Heap
| Arrays & String Descriptors |
| |
| -------------------------------------- <---
| Stack
| --------------------------------------
Data Group
(DGroup) Static Data:
(Near Memory) Internal Variables
| String Literals
| Data Statements
| Fixed Length Common Data
| Static Arrays
| Constants
| Scalar Variables
| User Defined Type Variables
| Fixed Length Strings
|
--> ====================================== <---
|
Program Code |
Low Memory
-------------------------------------- |
MS-DOS |
-------------------------------------- <---
QBX.EXE ENVIRONMENT (FAR STRINGS)
---------------------------------
The use of Far String addressing is the default in the QBX.EXE
environment. Although memory usage and data storage does differ
considerably from compiled versions of the same program.
The following is some information that applies uniquely to the QBX
(QuickBasic Extended) environment:
Conventional Memory:
QBX requires 315K + additional memory needed for Quick libraries
if loaded.
Extended Memory:
If you have a 64K block of High memory (Extended Memory) and
nothing else is loaded there (such as MS-DOS 5.0) then QBX will
automatically load 60K of its code here (assuming HIMEM.SYS is
loaded). This reduces the amount of conventional memory
required by the QBX environment to 255K.
Expanded Memory:
If expanded memory is available it will be used by the QBX.EXE
environment to store some of your program's code. Only Sub
and function procedures greater than 1K and less than 16K will
be stored here. Subprograms larger than 16K are stored in the
far heap in conventional memory in both Basic 7.0 and 7.1.
QBX.EXE from version 7.1 uses expanded memory more efficiently
than QBX.EXE from version 7.0. In 7.0, each subprogram from 1K to
16K in size uses a full 16K of expanded memory. In 7.1,
subprograms smaller than 16K will use expanded memory in 1K
chunks. In 7.1, if a subprogram is 2K in size, it will use only
2K of expanded memory. In Basic 7.0, a 1K Sub as well as a 15K
Sub will each require a 16K chunk of expanded memory.
To save memory in Basic 7.0, you should try to keep the size of
your procedures smaller than 16K but as close to 16K as possible.
In Basic 7.1, you should simply try to keep sizes less than 16K.
You can determine the size of your procedures from within the
environment by pressing <F2>. The number you see to the right
of each module name is the module's size.
NOTE: When you have one or more Sub or Function procedures, QBX
will require a one time overhead of two 16K blocks (32K) for its
own internal tables.
QBX.EXE Switches for Managing Memory:
/Ea:
This will tell QBX to store fixed length arrays in expanded
memory. These arrays must be between 512 and 16K in size. DO
NOT use this switch in combination with /Es.
/Es:
Use this switch when you have some mixed language routines in a
quick library that access expanded memory. If you do not use
this switch the memory may not be available to those routines
when they are called. DO NOT use this switch in combination with
/Ea.
/E:n
This will specify the amount of expanded memory to reserve for
use by QBX. If this switch is not used the default is all
available expanded memory. If /E:0 is used then no expanded
memory will be used and the /Ea and /Es switches are overridden.
/NOFrills
This switch reduces the functionality of the environment and
frees up to 19K more memory that the QBX environment would
otherwise use.
- No Utility menu and associated commands.
- No Options menu and associated commands.
- No Help menu and associated commands.
Other QBX.EXE notes:
- The code of QBX.EXE itself is located in Low memory.
- If a Quick library is loaded, its code is loaded in Far memory.
The Quick library's Scalar variables, Static arrays, and Common
variables are stored in DGroup.
- Program code is now located in the Far Heap.
- Static arrays (except those in Common Blocks) are now stored in
the Far Heap.
- Every Common Block has a variable table created for it of a
corresponding size. This variable table is created for every
module that this Common Block is listed in.
QBX ENVIRONMENT FAR STRINGS
---------------------------
MS-DOS --> -------------------------------------- <---
640K Communications Buffer |
Boundary -------------------------------------- |
Quick Library Code |
-------------------------------------- |
|
Far Heap: |
Dynamic Fixed Element Arrays |
File Data Block Segments |
User Program Code Far Memory
Static Arrays (not in Blank Common) |
|
-------------------------------------- |
|
String Segments (Variable Length Strings): |
Local & Temp. String Segment |
Module Level String & |
Named Common String Segment |
Blank Common String Segment |
Local & Dynamic String Array Segments |
|
--> ====================================== <---
| |
| Local Heap: |
| Array & String Descriptors |
| String Literals |
| Data Statements |
| Fixed Length Common Data Near Heap
| Static Arrays |
| Constants |
| Scalar Variables |
| User Defined Type Variables |
| Fixed Length Strings |
| |
| -------------------------------------- <---
Data Group Stack
(DGroup) --------------------------------------
(Near Memory)
| Static Data:
| Quick Lib Scalar Variables
| Quick Lib Static Arrays
| Quick Lib Common Variables
| Internal Variables
| Blank Common Static Arrays
|
--> ====================================== <---
QBX Code |
-------------------------------------- Low Memory
MS-DOS |
-------------------------------------- <---
DYNAMIC versus STATIC ARRAYS
----------------------------
A Dynamic Array is one whose size is defined at run time. This type
of array can be REDIMensioned during the execution of the program.
A Static Array is one whose size is defined at compile time and cannot
be changed at run time.
To make an array, Dynamic do any of the following:
- Use the REDIM statement when first creating the array:
REDIM A(20) AS STRING * 80
- Use a simple variable for at least one of the dimensions when
DIMensioning the array:
d = 20
DIM A(d,64) AS INTEGER
- Use the array in a COMMON statement and then DIMension it
(Dynamic arrays should *always* be placed after the COMMON
statement):
COMMON A()
DIM A(64)
- Use the REM $DYNAMIC metacommand before DIMensioning the array:
'$DYNAMIC
DIM A(64)
To make an array Static do any of the following:
- DIMension the array with a literal:
DIM A(64) AS INTEGER
- DIMension the array with a CONSTant:
CONST Num = 16
DIM A(Num) AS INTEGER
- Implicitly DIMension the array (just start assigning values to
elements of an array). Note, when implicitly DIMensioning an
array you are limited to a default of 10 subscripts:
A%(0) = 5
A%(1) = 6
A%(2) = 7
- Use the REM $STATIC mettacommand:
'$STATIC
DIM A(64)
HUGE ARRAYS
-----------
Huge arrays are arrays that are greater than 64K in size. These huge
arrays must be dynamically DIMensioned and must contain elements of a
fixed size. In order to use huge arrays you must use the /AH option
for the QBX environment and/or for the Compiler.
To DIMension an array Dynamically you can either REDIM the array when
first created, use the REM $DYNAMIC mettacommand, DIMension the array
with a variable, or DIMension the array after a COMMON Statement in
which you reference that same array. (See previous section on Dynamic
versus Static arrays).
An array of "Fixed-elements" refers to an array whose elements are any
numerical type (Integer, Long, Single, Double, Currency), an array
whose elements are fixed length strings (A as String * 16) or an array
whose elements are a user defined type (NOTE: a user defined type can
not contain variable length strings).
If the array is greater than 128K, the size of each element of the
array must be a power of two (such as 2, 4, 8, 16, 32, 64, 128, 256,
and so forth). Since all numerical types have a size that is a power
of two, all you need to consider are elements that are either fixed
length strings or user defined types.
With a user defined type, it is not important for each field in the
type to be a power of two in size, but rather for the total size of
the type be a power of two. For example:
Type testtype
x as integer
y as long
End Type
This user defined type has a size of 6 bytes, which is not a power of
two. You must "pad" the type with additional bytes:
Type testtype
x as integer
y as long
dmmy as string * 2
End Type
Total size is now 8, which is a power of two (2^3).
The field used for padding can be any type, but a fixed length string
is especially convenient for padding.
The above padding is necessary in huge arrays because an array element
may not span a segment boundary. That is, each
element of an array must fit in memory in such a way that they
completely fit within a segment's boundary (aligned with the segment
boundary) with no leftover space or gap.
For example, if we have an element 32K in size, two elements will fit
evenly in a single segment (with no gaps). But if we have an element
31K in size, two elements will fit in one segment but others will have
to start in another segment, leaving a 2K gap in memory.
| 2k | element1 | element2 || element3 | element4 | 2k |
The segment boundary is between element2 and element3. In order for
the elements to be aligned with the segment boundary, Basic
automatically shifts the first two elements in memory. Note the 2K
gaps and the fact that no more records can be boundary-aligned after
element4.
Because of this requirement of boundary alignment, if the elements of
a huge array are not a power of two in size, the largest size this
huge array could be is under 128K. Otherwise, if the elements are a
power of two and thus able to be segment boundary aligned, the size
of the huge array is limited only by available conventional far memory
(MS-DOS 640K).
SIZE LIMITS
-----------
Data Types Maximum Minimum
---------------------------------------------------------------------
Variable names 40 characters 1 character
String length ($) 32,767 characters 0 character
[1 byte per char]
Integers (%) 32,767 -32,767
[2 bytes]
Long Integers (&) 2,147483,647 -2,147,483,648
[4 bytes]
Currency (@) 922,337,203,685,477.5807 -922,337,203,685,477.5808
[8 bytes]
Single-precision (!)
[4 bytes]
Pos 3.402823E+38 1.401298E-45
Zero 0
Neg -1.401298E-45 -3.402823E+38
Pos with /Fpa 3.402823E+38 1.175494E-38
Zero with /Fpa 0
Neg with /Fpa -1.175494E-38 -3402823E+38
Double-precision (#)
[8 bytes]
Pos 1.797693134862315D+308 4.940656458412465D-324
Zero 0
Neg -4.940656458412465D-324 -1.797693134862315D+308
Pos with /Fpa 1.79769313486232D+308 2.2250738585072D-308
Zero with /Fpa 0
Neg with /Fpa -2.2250738585072D-308 -1.79769313486232D+308
Arrays Maximum Minimum
----------------------------------------------------------------------
Array size (all elements)
Static 65,535 bytes (64K) 1
Dynamic Available Memory (MS-DOS 640K) 0
Array Dimensions 8 1
Array subscripts 32,767 -32,768
Files & Procedures Maximum Minimum
---------------------------------------------------------------------
Number of arguments 60 interpreted 0
Nesting of include files 5 levels 0
Procedure size (interpreted) 65,535 bytes (64K) 0
Module size (compiled) 65,535 bytes (64K) 0
Overlay size 128K 0
Data Files open simultaneously 255 0
Data file record number 2,147,483,647 1
Data file record size(bytes) 32,767 (32K) 1
Data file size Available disk space 0
Path names 127 characters 1
HOW TO DETERMINE AVAILABLE MEMORY
---------------------------------
There are two Basic Functions you can use to find out how much of
certain kinds of memory is available. These are the FRE Function and
the STACK Function.
The value returned by FRE is determined by the argument passed to it,
as well as weather or not you are using Far String addressing. Here
are some examples:
Function Output with Near Strings Output with Far Strings
----------------------------------------------------------------------
FRE(A$) Unused space in DGroup Unused space in segment
where A$ is stored.
FRE("") Unused space in DGroup Unused space for
temporary strings.
FRE(-1) Total unused memory Total unused memory
FRE(-2) Unused Stack space Unused Stack space
FRE(-3) Available EMS Available EMS
(expanded memory) (expanded memory)
NOTE: FRE(-2) returns the amount of Stack space never used by the
program, sort of like a high water mark for the Stack. The value
returned will get smaller as you program uses more of the Stack. If
the amount of Stack space being used decreases, the value returned by
this function will not change. The value of FRE(-2) may not be
accurate when used in the immediate window of the QBX environment.
The STACK function returns the maximum Stack size that can be
allocated. This is a combination of the unused Stack space of the
current Stack setting as well as the rest of the unused space in
DGroup.
To obtain the amount of free DGroup memory when using Far String
addressing use the following equation:
FreeDG& = STACK - FRE(-2)
WHAT TO DO WHEN YOUR PROGRAM RUNS OUT OF MEMORY
-----------------------------------------------
In General:
- Reduce size of your MS-DOS buffers (in CONFIG.SYS)
- Eliminate any terminate-and-stay-resident (TSR) programs
- Eliminate non-essential device drivers under MS-DOS.
- If in the QBX environment, unload any files that are not needed
for your program.
- Make your program smaller.
- If you have Huge arrays (>64K) make sure to use the /Ah option
(see previous section on Huge arrays).
- Make sure your arrays have elements of a fixed size.
- When compiling with BC.EXE, do not use /D if possible.
- When linking use stub-files whenever possible.
(pg. 538-539 "Basic 7.0: Programmer's Guide" for 7.0/7.1) .
- Use a smaller File buffer in the OPEN statement's LEN clause.
- Use Link Overlays. (pg. 612-614 "Basic 7.0: Programmer's
Guide").
DGroup Memory:
You may be out of space in DGroup even if you still have space
left in Far memory and/or extended memory.
Use the following statement to find out how much DGroup you
have left: PRINT STACK - FRE(-2)
- Reduce stack size to the minimum that your program requires
(Using the CLEAR Statement, see manual).
- Use Fixed Length strings whenever possible
- Use fewer string literals and DATA statements, read data in
from a file.
- Use DEFINT A-Z at the top of your modules. This makes the
default data type integer.
- Declare your arrays as DYNAMIC
(see previous section on Dynamic versus Static).
- Use Far sting option (/Fs)
- If in the environment, put your Functions and Procedures into a
Quick library.
- NOTE: within the QBX environment
every Common block has a variable table created for it of a
corresponding size. This variable table is created for every
module that this Common block is listed in. So do not list a
Common block in a module unless you need it to be there.
Extended Memory:
Only the QBX environment will take advantage of Extended memory.
- Make sure it is available. Load QBX without the extended
memory driver. Execute PRINT FRE(-1) in the Immediate window,
note the value returned, exit QBX. Install the extended memory
driver. Load QBX and execute PRINT FRE(-1) as before. If
extended memory is available the second value should be about
60K greater than the first.
Expanded memory:
Only the QBX environment and a compiled program using Overlays
will take advantage of Expanded memory.
- Make sure expanded memory is available. Load QBX and execute
PRINT FRE(-3) in the immediate window, which should return the
amount of available Expanded memory. If it returns an error
"Feature unavailable", then you either do not have expanded
memory or the driver is not loaded or loaded incorrectly.
Check the "Getting Started" manual under "Memory Management of
QBX" for more information.