Turbo C++ XMS Interface Library
-------------------------------
Version 1.1
by Richard Vuduc
Copyright 1990, Richard Vuduc

Contents
--------
Disclaimer................................................. 1

The Beginning.............................................. 1

Where To Contact Me........................................ 1

Introduction............................................... 2

Implementation............................................. 2

HIMEM.SYS.................................................. 3

Overview of the Product.................................... 3

XMSDriver Example.......................................... 4

XMSHandle Example.......................................... 5

Intro to the Routines...................................... 6

XMSDriver Class............................................ 6
                      XMSDriver, Inst, GetAvail,
                      GetTotal, InstHMA, ReqHMA............ 7

                      RelHMA, GetHMAPtr, A20Enable,
                      A20Disable, A20LEnable, 
                      A20Disable, A20Query, HMAWrite....... 8

                      HMARead, EMBAlloc, EMBFree,
                      EMBLock.............................. 9

                      EMBUnlock, EMBMoveToExt, 
                      EMBMoveToCon, EMBExtToExt,
                      HndReqPtr, HndRelPtr.................10
XMSHandle Class............................................11
                      XMSHandle............................11

                      ~XMSHandle, Realloc,
                      Bounds, GetPtr, SetPtr, IncPtr,
                      SetSwap, GetHandle, GetSize..........12
 
                      Get, Store, CRealloc, GetCache,
                      SetCache, CBounds....................13

                      CacheRead, CacheWrite,
                      CacheUpdate..........................14

Writing Your Own Get and Store Routines....................14

The End....................................................14

----------
DISCLAIMER
----------
There's no real need for this section; it just makes it look more
official.  The only thing I can think of to put here is that if
there's something wrong in here, too bad.  No false statements were
put in here on purpose.  And, if the earth implodes because something
I wrote in here, so be it.  I will not be responsible.

-------------
THE BEGINNING
-------------
Yes, friends, my name is Richard Vuduc and I am 15 years old (not
that that's important or anything, I just thought it was worth
mentioning).  And, for your viewing pleasure, I have assembled a
library of neet little tools to help you make use of extended memory.

Early this summer, I bought a copy of Turbo C++.  Here, I would like
to present something I threw together for other owners of Turbo C++
(and soon, if I get a reasonable response, owners of a lot of other
compilers, C or otherwise, including Pascal and Assembly).

-------------------
WHERE TO CONTACT ME
-------------------
Forward your messages to me at my CIS account (actually, it's the
account of the place where I work, but I use it for them):

		Richard Vuduc, CIS 70022,1332

I LOVE getting mail, so please send something.

Also, there's no charge for this library of routines.  If you like
them and want to send money to a starving high school student, who
has nothing but the clothes on his back and some loose leaf notebook
paper (keep in mind school starts soon), please feel free to send
donations of money ($1.00 and up, please) to me at:

	Richard Vuduc
	3711 S. George Mason Drive, Suite C-1-W
	Falls Church, Va 22041

You can, if you want, send me a check made out to your favorite
charity and I'll forward it to them.

To register yourself, please leave a note for me at my CIS account
and I'll do so.  Senders of money should also leave a note to let me
know it's coming and for immediate registration.  You don't have to
pay to register (but if you want to see another kid go to college,
you better send something).  Also feel free to distribute this
elsewhere PROVIDED you DO NOT charge anyone for this.  That's my
job, so DON'T DO IT.
                               - 1 -
-----
INTRO
-----
You here all about extended memory in all the IBM computer magazine
headlines these days - but what is it, really?  How can I use it? 
What good is it?  I wondered these same things as well, and I
decided to go out and find out instead of partying all summer. 
Here's what I found...

	- There are a couple of ways to access extended memory.  Most
AT's have BIOS routines (int 15h) that provide a means by which to
access extended memory.

	- Microsoft's eXtended Memory Manager (XMM) is another way to
access extended memory.  Windows 286 and Windows 3 have HIMEM.SYS
bundled with them.  Not only do they allow access to extended memory,
but also to the High Memory Area (HMA), and the Upper Memory Area
(UMA, 640K to 1024K).  This is (I'm sure you know this already) the
area just above the 1MB region.  More on this later.

	- Using the VDISK interface protocol.  This is basically access
to extended memory just like VDISK would.

	- Some VCPI thing is another way.  I think you need a 386 for
this one (something I do not currently possess in my home), and is
really a DOS Extender (you can look it up yourself).

Anyway, I wanted to be able to access extended memory, too.  Why
extended memory?  Well, it seems like such an untapped resource
(unlike EMS, which you can get three zillion articles on), I decided
to do the public a favor and assemble a FREE library you can use it
with (see the about the author section for more details).  Since I
also have a new compiler (Turbo C++, and the other Tools like the
Turbo Debugger and Assembler), I decided to take this opportunity to
do something great and spectacular and, well, just something "me."

--------------
IMPLEMENTATION
--------------
One day I sat down and thought about which of the four methods for
accessing extended memory I should use.  It occurred to me that the
easiest one to implement were the BIOS routines.  Unfortunately, I
decided against that for now.  The BIOS routines were clunky to use;
I eventually opted for the Microsoft HIMEM.SYS method.

Two things brought me to HIMEM.  The first was the fact that I did
have the HIMEM driver (with Windows 286 and the upgrade to 3).  I
also saw an article on writing a RAM disk thing using HIMEM as the
interface.  Great - I had example code so this should be easy. 
Unfortunately, the article didn't document much, so I continued
onward looking for more documentation...and eventually found
HIMEM.ARC, available in the Microsoft Systems Forum on CompuServe.

Using the document in there, I continued to code (in assembly for
low-level routines, and used C++ for the upper level routines) and
created a single class for managing extended memory - XMSDriver.

Although I'm not releasing the source code yet (a teenager is very
self conscious about zits, hair, clothes, and code), I don't want to
                             - 2 -
get harrased about how this C++ class isn't a good object oriented
class design, blah, blah blah...  I realize I didn't design the
heirarchy well (there's only one method), but I didn't need to.  The
real purpose of putting into a class was to encapsulate all the code
and internal data without using globals, etc.  Classes were the
perfectly neet way to put everything together in one package.  So
there.

-----
HIMEM
-----
Here's some background info on the theories behind HIMEM.  If you
think you already know enough, so be it.  If you need more info, you
can also get HIMEM.ARC from the MSSYS forum on CompuServe (I'm sure
it's on BIX, too).

Okay, here's the run down on what HIMEM is supposed to do for the
average "joe programmer":

	- HIMEM will provide a standard method to gain access to the
loads and loads of extended memory that exist in 286, 386, and 486
machines (and I'm sure for the 586, 686, 786, 886, 986, and the
81086, 81186, 81286, 81386,...88086, 88186, 88286,...90086,...).  It
should also take care of fragmentation and provide support routines
for accessing, allocating, releasing, and moving extended memory.

	- HIMEM will provide a standard method by which to access Upper
Memory Blocks.  UMB's are the area betwee 640K and 1024K (normally
used as video memory, LAN and EMS space, BIOS, etc.  Basically, it
returns a segment pointer to that block.

	- HIMEM let's you use and execute code from the High Memory Area. 
The HMA is the area between 1024K and 1088K without leaving real
mode.  It does it like this:  Let's say you have the segment:offset
pointer 0xFFFF:0000.  On 286 and 386 systems, you have enough address
lines to access more memory than a regular old 8086.  On 286+
machines, you can enable the 21st address line (8086's have only 20
address lines) and access a little more extended memory, making this
a valid pointer -> 0xFFFF:0BA3.  On 8086's, this would just wrap
around to the beginning of RAM.

--------
OVERVIEW
--------
In this section, I want to present a brief overview of all the neet
things you get with this library, and what you're supposed to do to
them.

In the library, I have defined two main classes: XMSDriver and
XMSHandle.  XMSDriver is the class which you create an instance of to
handle the nitty gritty details of extended memory interface, and
which you also use to perform low-level extended memory management. 
XMSHandle is one step above XMSDriver.  Basically, you use XMSDriver
to allocate a block and get a pointer to XMSHandle.  Then, you use
XMSHandle to get and store data, etc. in extended memory.  If you
want to use the High Memory Area, however, you will have to currently
use XMSDriver.  Generally, you'll probably use the XMSHandle class
for all your extended memory managment routines.  For the low-level
stuff, you'll have to use XMSDriver (after I present the general
                             - 3 -
outline steps for using each one, you can decide for yourself which
one to use).

The XMSDriver class implements most of the HIMEM functions directly,
i.e., most of the XMSDriver functions have a corresponding HIMEM
function (if you look at the XMS spec in HIMEM.ARC, you'll see the
amazing likeness in function).  This is the main reason I call it
the 'low-level managment routines' class.  You should usually only
create ONE instance of the XMSDriver class globally - it's not really
intended to be created multiple times (just as is the case with
HIMEM.SYS itself - I don't think you can load two or twenty-five
copies of HIMEM.SYS at any one time on one machine).  Then, once you
have created an instance of XMSDriver, you simply reference it as you
would any other class and call functions.  Here is a sample of
creating an instance, and it's usage:

XMSDriver
---------
// SOMEPRG.CPP - Does something
#include <iostream.h>
#include "xms.h"			// You MUST include this header file
						// to use the XMSDriver class.  There
						// are two header files in the library,
  ...					// but the other one is included inside
						// this one (and is for use with
						// XMSHandle).
XMSDriver XMM;			// Create an instance of the XMSDriver
int something;			// class.  You should do this globally
   						// for access in ALL routines (although
	...					// the demo1 program has it in 'main()'

void main( void )
{
  int handle;				// You can declare variables to hold
						// the handle number when you request
	...					// extended memory blocks

  if( ! XMM.Inst() )		// Determine the presence of HIMEM.SYS
  	err_disp( NO_XMS );	     // this would be called when there's no
						// HIMEM.SYS

  if( XMM.GetAvail() < 64 )  // Determine whether or not there's
    err_disp( NOT_ENOUGH );  // enough ext mem available for your app

//*********High Memory Area Management
  if( ! XMM.ReqHMA() )		// Use this function to get the HMA
	err_disp( NO_HMA );		// this is called when there is no HMA

  XMM.A20Enable();			// To use the HMA, turn the A20 line on
  
  hma_ptr = XMM.GetHMAPtr();	// Get the HMA pointer to access HMA

  *hma_ptr = 'A';			// Manipulate the pointer
						// Alternatively, you could use the 
	...					// HMARead and HMAWrite functions for a
						// more independent method

  XMM.A20Disable();		// When you're finished, turn A20 off
  XMM.RelHMA();			// and release the HMA for someone else
                                - 4 -
//*******Extended Memory Block Managment Routines
  handle=XMM.EMBAlloc(10);	// Here, a 10K block has been allocated
						// in extended memory.  'handle' would
  if( !handle )			// be zero if the request failed
	err_disp( EMB_ERR );

  XMM.EMBRealloc( handle, 12 );			// Reallocate blocks, move blocks
  XMM.EMBMoveToExt( handle, 0, ptr, 35 );	// to and from extended memory
  XMM.EMBMoveToCon( ptr, handle, 0, 35 );
	...
  XMM.EMBFree( handle );	// Free the extended memory block 
  cout << "Done...So what did you think?\n";
}

---------
XMSHandle
---------
As I have said more than I need to, the XMSDriver is fairly low-level
as far as extended memory management is concerned.  It is for this
reason that I created the XMSHandle class.  You still use EMBAlloc to
allocate a block, but then you call XMSDriver::HndReqPtr and you get
a pointer to the XMSHandle class.  This pointer is simply a handle to
the extended memory block you allocated with EMBAlloc.  Once you have the pointer to
XMSHandle class, you can start using higher-level routines.  These
higher level routines allow you to get individual items from extended
memory.  It basically treats the EMB as a huge array that can contain
any number of different data items that you can retrieve in any
order.  You use routines like Get( item ) and Store( item ).  The Get
and Store routines have been overridden so you can store multiple
different items.  For example, there are Get and Store routines for
characters, longs, integers, and also large blocks of data (void
pointers).  I've included the source for the overridden Get and Store
routines so you can write your own Get and Store routines for other
data types including structures and other classes.  Here's the
breakdown on the procedure...

// SOMEPRG1.CPP - Does something else
#include <iostream.h>
#include "xms.h"			// You include this header file as
	...					// usual.  It's essentially the same
						// procedure as with the 'XMSDriver'
						// class
XMSDriver XMM;			// Create an instance of the XMSDriver
XMSDriver *xmsptr = &XMM;	// AND a GLOBAL pointer to that instance
						// that MUST be called 'xmsptr.'
	...					// The routines in XMSHandle need to
						// access the XMSDriver, so they do it
						// via a pointer to any instance of it.
void main( void )
{
  long temp;
  int handle;				// You can declare variables to hold
  XMSHandle *hptr;			// the handle number when you request
	...					// extended memory blocks (just as
						// before).  You also need to create
						// a pointer to an XMSHandle.

  if( ! XMM.Inst() )		// Determine the presence of HIMEM.SYS
  	err_disp( NO_XMS );	     // this would be called when there's no
						// HIMEM.SYS

                            - 5 -
  if( XMM.GetAvail() < 64 )  // Determine whether or not there's
    err_disp( NOT_ENOUGH );  // enough ext mem available for your app

//*******Extended Memory Block Managment Routines
  handle=XMM.EMBAlloc(10);	// Here, a 10K block has been allocated
						// in extended memory.  'handle' would
  if( !handle )			// be zero if the request failed
	err_disp( EMB_ERR );

  hptr = XMM.HndReqPtr(handle);	// Get the handle pointer.  You can
							// now use hptr to access the EMB

  hptr->Realloc( 20 );			// Reallocate to 20K
  hptr->SetPtr( 5 );			// Set the index in the array
  hptr->Get( temp );			// This gets the 6th element in the
							// EMB (EMB[5], 5 set at SetPtr(5))
	...
  hptr->Store( 100L );			// Store a number up there.

  XMM.HndRelPtr( hptr->GetHandle() );	// Release the pointer when
								// you've finished with it
  cout << "No more...Go bye bye!\n";
}

The XMSHandle class is more flexible with extended memory transfers. 
You can define your own Get and Store routines (examples later) and
it's probably preferrable that you use XMSHandle (of course, I
couldn't care less which one you use).

------------
THE ROUTINES
------------
I have not implemented the Upper Memory Block functions yet; I need
to get something started and distributed so I can get a feel for the
response.  For more info on High Mem, just get a copy of HIMEM.ARC on
the Microsoft Systems Forum (go MSSYS, I think).

Note that there are some "private:" functions in the XMSDriver class.
These are for internal function use ONLY.

When you want to access some extended memory, you need to declare a
class variable (called an "instance," I guess) called whatever you
want to call it (check out the demo, XMSDEMO.CPP, for an example), of
type XMSDriver.  Then you can start calling methods...
* Example:

void main( void )
{
	XMSDriver XMM;                  // Declare an XMM
	...
	Available = XMM.GetAvail( );    // Get available memory
	if( !XMM.ReqHMA( ) )            // Requesting HMA...
	...
}

I have, in the header file, declared an enumerated type called
'boolean'.  The values in this are True and False.  I hope they don't
                                - 6 -
collide with any booleans you declare.  They are used internally and
as return values for various functions in the two classes.

Also note that I have not implemented any serious error checking.  If
a function returns False (meaning an error occurred), you'll be hard
pressed to find out why.  This is easy to implement - I'm sorry I
didn't do it.  Yeah, yeah, yeah...I know there isn't any way to find
out currently, aside from dumping the registers (the error code is
in the BL register).

Below, I have the detailed list of all the functions in the XMSDriver
and XMSHandle functions.  All the XMSDriver functions come first.

XMSDriver::XMSDriver( void ) and XMSDriver::~XMSDriver( void );
---------------------------------------------------------------
These are the constructor and destructor for the driver (wa la!). 
This constructor performs all the necessary initialization functions,
including determining the presence of the HIMEM driver.  The
destructor, when run, does not automatically go through and release 
any unreleased XMS handles that are still open.  You must do this
explicitly.

boolean XMSDriver::Inst( void );
--------------------------------
This routine returns True if HIMEM is installed, and XMSDriver has
set everything up for using it; otherwise it returns False if HIMEM
is not installed.

long XMSDriver::GetAvail( void );
---------------------------------
This routine returns the largest available memory block.  Note that
this is not always the same as the Total amount of memory installed
or the total amount of memory free (confused?).  Because extended
memory can be fragmented, this only returns the largest availble
unfragmented block.

long XMSDriver::GetTotal( void );
---------------------------------
Upon return, this function returns the amount of extended memory
free.  This is greater than or equal to the largest available block
of extended memory.

******************* High Memory Functions ***************************
boolean XMSDriver::InstHMA( void )
----------------------------------
This function determines whether or not the High Memory Area has been
allocated.  It returns True if the high memory area is available,
False if not.

boolean XMSDriver::ReqHMA( void )
---------------------------------
This function requests use of the High Memory Area from HIMEM.SYS. 
It returns True if HMA access is granted to the application, False
otherwise.  Only allocate the HMA if you plan to use a good portion
of it, as other applications may need the HMA.  The HMA is only given
out as one large block to an application, so use it wisely.  Also,
don't forget to cleanup after yourself and release the HMA when you
are through.

                                - 7 -
void XMSDriver::RelHMA( void )
------------------------------
This function attempts to free the High Memory Area.  There is no
return, because the only way the High Memory Area could be not
released is if you didn't own it in the first place, or if it didn't
exist.

void *XMSDriver::GetHMAPtr( void )
----------------------------------
This function returns a pointer to the High Memory Area, or NULL if
the Area is not available.  Use the pointer returned by this function
WITH CAUTION.  Many of the MS-DOS routines will normalize these
pointers and wrap the addresses around.  For safety's sake, try and
use the HMARead and HMAWrite functions, clumsy as they are.

// ********A20 line functions
Recall that the HMA is possible because the 286+ machines have
additional address lines (the 21st line in particular).  The A20 line
is off on the 286 by default, because some applications depend on the
wrap around at the top (I have no idea what applications depend on
this, but that's what Microsoft says).  In order to use the HMA, you
must enable the A20 address line.  Don't forget to disable it after
you are through.

boolean XMSDriver::A20Enable( void )
------------------------------------
This function attempts to enable the A20 line so folks like you can
access the HMA.  It returns True when the A20 line has been
successfully enabled.

void XMSDriver::A20Disable( void )
----------------------------------
This function disables the A20 line.  There is no return value.

boolean XMSDriver::A20LEnable( void )
-------------------------------------
This function enables the A20 line locally.  I'm not quite sure what
this means, but I suspect it's so you can enable the line and not
worry about the state of line currently.  In other words, you can
enable the line for your own purposes, regardless of the current
state of the A20 line.  You need to perform a local disable when
you're through.

void XMSDriver::A20LDisable( void )
------------------------------------
"Locally Disable the A20 line" this function has been dubbed (I had
to use a different intro so you wouldn't get too bored).  There is no
return.

boolean XMSDriver::A20Query( void )
-----------------------------------
This function queries the state of the A20 line.  It returns True if
the A20 line is enabled, False otherwise.

void far *HMAWrite( unsigned ItemSize, unsigned Index, unsigned Length,
				void far *Data );
----------------------------------------------------------------------
This function is kind of involved, but I'll explain it as best I can. 
Basically, it's a high level write to high memory area function.  It
                              - 8 -
might be simpler to just maintain a pointer to the High Memory Area
(the HMA begins at 0xFFFF:000A; 64k - 16 bytes long) than to use this
function, but it's up to you.  This function treats the "Data" (last
parameter) as an array.  Each array element is ItemSize long.  The
function will begin copying at Data[ Index ], and will copy Length
elements of the array (including that point).  It returns a pointer
to the High Memory Area pointing to the first element if Data that
was copied, or NULL if the function failed.

void far *HMARead( unsigned ItemSize, unsigned Index, unsigned Length, 
				void far *Data );
----------------------------------------------------------------------

This function is similar to HMAWrite except that it reads from the
High Memory Area, and returns a pointer to Data. It treats the High
Memory Area as an array, each element is ItemSize bytes long.  It
copies Length elements starting at the beginning of the High Memory
Area + Index (in other words, it starts reading at Index offset into
the HMA).  It copies all this junk into Data, and returns the pointer
to Data (NULL on failure).

**End of High Memory Area Management Functions (what a mouth full)

**************** Extended Memory Block functions
The following functions allow the management of extended memory. 
Basically, you call a function requesting some extended memory.  The
function returns a handle to that memory, and you use that handle to
access the memory block later.  Every time you allocate a block of
extended memory, you are getting what's called an Extended Memory
Block (EMB).  You must use the handle to use the EMB.  For your
convenience, I have provided some routines to copy blocks of data to
and from extended memory.  Actually, I think that you are forced to
use them, whether or not you want to.

By the way, one of the Extended Memory Block Management Functions in
Microsoft's HiMEM.SYS is not implemented.  That is function 0x0E -
Query Extended Memory block.  It's used to get info on the Extended
Memory Block.  I will implement it later, so don't cry.

NOTE: For from-to extended memory transfers, 'size' must be even. The
function will not succeed unless size is even.  It's also advisable to
word align data you store in the EMB because they generally run much
faster on 2/3/486 machines.

unsigned XMSDriver::EMBAlloc( unsigned size )
---------------------------------------------
This routine will try as hard as it can to allocate a block of
extended memory 'size' bytes in length.  It returns a handle to the
EMB on success, otherwise it will return 0.  The location of this
block in extended memory is not known as it may be moved around by
the driver to relieve fragmentation.

void XMSDriver::EMBFree( unsigned handle )
------------------------------------------
This function frees the handle specified by 'handle.'  There is no
return.

long XMSDriver::EMBLock( unsigned handle )
------------------------------------------
                               - 9 -
I'm not totally sure I understand what this is for, but I'll take a
stab.  This function locks a previously allocated EMB.  Locking the
block guarantees that the HIMEM driver will NOT move it around.  This
way, you can get direct access to the block using the BIOS routines,
or whatever.  This function returns a 32-bit linear address pointer
to the extended memory block, or 0 if an error occurred.

boolean XMSDriver::EMBUnlock( unsigned handle )
--------------------------------------------
This function releases the lock that was put on the handle
previously.  Lock counts are maintained, meaning if you lock a block
3 times, you have to unlock it 3 times as well before it is actually
unlocked (I guess).  It returns True on success, False on failure (so
don't make me feel like a failure and send money or 10 pages of
praise) (just kidding) (not) (way) (no way)...

boolean XMSDriver::EMBMoveToExt( unsigned dhandle, unsigned doff,
						    void far *p, long size );
-----------------------------------------------------------------
This function moves a block of conventional memory to extended
memory.  Actually, it really copies the block as opposed to freeing
it.  dhandle is the handle of the destination EMB, and doff is the
offset into the EMB to begin the transfer.  It returns True if the
move was a success, False if it failed.  By the way, 'size' is the
size of the block to move in bytes.

boolean XMSDriver::EMBMoveToCon( void far *p,
						    unsigned shandle, unsigned soff,
						    long size );
------------------------------------------------------------------
This function moves a block of extended memory to conventional memory
(the pointer *p).  shandle is the handle number of the source EMB,
and soff is the offset into the shandle to begin copying.  'size' is
the size in bytes to transfer.  Returns True on success and False on
failure (say, you wouldn't happen to see a pattern in the return
values, would you?).

boolean XMSDriver::EMBExtToExt( unsigned dhandle, unsigned doff,
						   unsigned shandle, unsigned soff 
						   long size);
--------------------------------------------------------------------
This function copies data from one EMB to another EMB.  dhandle and
shandle are the destination and source handles respectively (gee, i
bet you would've never guessed that) and doff and soff the offsets
into the block.  'size' is the size in bytes of the transfer.  It
returns True on success, and False on failure.

XMSHandle *XMSDriver::HndReqPtr( unsigned handle );
----------------------------------------
This function returns a pointer to one of the EMB in the form of the
XMSHandle class.  After receiving this pointer, you can use the
XMSHandle functions (described below).  For most, the XMSHandle
routines will provide barely reasonable and tolerable functions for
extended memory block manipulation.

void XMSDriver::HndRelPtr( unsigned handle );
----------------------------------
Call this routine with the handle number (you can use one of the
XMSHandle routines to get the handle number) to release the block
                            - 10 -
back to HIMEM.  It is recommended that you always implicitly do this
(although the XMSDriver destructor will destroy them all for you) in
order to keep memory free for other things.

   ***************** The XMSHandle Functions *****************
The XMSHandle classes has various functions for manipulating extended
memory, and an item you might find interesting - a conventional
memory data cache.

"A cache? What for?"  Well, as you will discover, the Get and Store
routines are overloaded so that you can retrieve single data items at
any time.  HIMEM.SYS is optimized for large, block moves.  As a
matter of fact, it moves large blocks fairly quickly (even on my
lowly 8mHz 286).  In XMSDEMO2.CPP, you will see the function declared
called 'fill_emb.'  This function is designed to demonstrate the
single data item transfer of the Get/Store routine.  However, the
overhead involved to move a single data item slows performance
incredibly, because the HIMEM driver requires that a parameter block
be setup with pointers here and there...a heck of a lot for just one
byte.  For my small applications, anyway, I just need to operate on
an individual data item basis, so I designed this class around that. 
To speed up transfers, I use a small conventional memory cache to
allow quick updates of extended memory, and quick accesses to the
data there.  I also designed the Get and Store routines so that they
can be overloaded to handle other data types besides the one's that I
have setup - char, int, and long.

Before I get into the detail regarding the individual members, I will
attempt to explain how XMSHandle is setup.  After all the functions,
I will explain how to overload the Get and Stores for your own
datatypes.

The XMSHandle class has control over a single EMB.  Inside the
XMSHandle class is a variable called 'Handle' that allows it to
access that data through the XMSDriver.  To access that data, it uses
the internal Handle, and calls the XMSDriver through the pointer
called 'XMSDriver *xmsptr.'  This is why you have to declare this in
your programs.

The XMSHandle treats it's EMB as a huge array.  Internally, it keeps
a counter called 'Index.'  'Index' points to a byte offset within
the extended memory block.  Using this index as a pointer, I am
assuming that the data kept in extended memory is going to be
accessed pretty heavily as a linear block (as in the example program,
XMSDEMO2.CPP), or array.  When you use the single item Get and Store
routines, they use 'Index' as the place to get data from.

Here are all the functions, as you ordered.

****Standard management functions

XMSHandle::XMSHandle( unsigned csize );
---------------------------------------
The constructor for the class excepts one parameter - the size of the
conventional memory data cache, in bytes.  The constructor is called
by the XMSDriver EMB allocation routine when an allocation request
has been made.  csize is set to 50 by default.  If there is not
enough memory to set up a 50 byte cache, it will reduce the size
attempt to 10 bytes.  If a 10 byte request fails, the cache will not
                                - 11 -
be set up at all.  The cache may be any size up to the size of the
EMB itself.  The cache size MUST be even for the XMSMove functions.

XMSHandle::~XMSHandle( void )
-----------------------------
This destructor releases the cache set up before (if it was setup).

boolean XMSHandle::Realloc( unsigned ksize )
--------------------------------------------
This function will attempt to reallocate the EMB.  'ksize' is the new
size of the cache in bytes.  If the Realloc function fails, it
returns False, and you must call CRealloc to reallocate the cache
(which gets destroyed before the call to the HIMEM reallocation
function).  Otherwise, the block's size is readjusted.  If the size
of the block reduces so that it's size is less than that of the
cache, the cache size will be reduced to the size of the extended
memory block.  It returns True if it succeeds.

boolean XMSHandle::Bounds( long i )
-----------------------------------
This function tests 'i' and determines whether or not it is within
the boundaries of the extended memory block.  Typically, this
function is called internally in SetPtr to determine whether the
requested pointer is within the range of the block.

long XMSHandle::GetPtr( void );
-------------------------------
This function returns the position of the pointer 'Index' currently.

boolean XMSHandle::SetPtr( long i )
-----------------------------------
Use this function to change the current index pointer to another
place within the EMB.  If 'i' is outside the boundaries of the
Extended Memory Block, it returns False and does not reposition the
pointer.  Otherwise (if successful), it returns True.

void XMSHandle::IncPtr( long size = 0 )
-----------------------------------
This function increments the current pointer by 'size' bytes.  If
size is set to zero (the default), the pointer will increment by only
one byte using the ++ operator (for faster increments).  Otherwise,
size is added to 'Index.'  If that sum is outside the range of the
extended memory block, the 'Index' pointer will wrap around (e.g., if
Index is 10, the size of the EMB is 12, and 'size' is 5, the index
pointer will be placed at 3).

void XMSHandle::SetSwap( boolean s )
------------------------------------
This function is not currently implemented.

unsigned XMSHandle::GetHandle( void )
-------------------------------------
This function returns the handle number of the EMB that this function
controls.

long XMSHandle::GetSize( void )
-------------------------------
This returns the size, in bytes, of the extended memory block.

                              - 12 -
*****Get and Store functions
boolean XMSHandle::Get( void far *a, unsigned& len, long off = -1 )
-------------------------------------------------------------------
This will retrieve a large block of memory from extended memory and
copy it to conventional memory, into the pointer 'a'.  len is a
reference parameter that may be modified to show the true length of
the copied block.  'off' indicates where in the extended memory block
the data should be read from.  If 'off' is not specified (the default
value of -1 is used), or if 'off' is explicitly set to -1, then the
data read will occur from the current index pointer.  The function
returns False if the data could not be read, or True if the function
was successful.

void XMSHandle::Get( char& a )
void XMSHandle::Get( long& a )
void XMSHandle::Get( int&  a )
------------------------------
These three functions are used to manipulate the data on a single
data item by single data item basis.  A variable 'a' is read from the
extended memory block beginning at the current 'Index.'  That value
is stored in 'a', with the appropriate type cast.  You may overwrite
these with your own functions.

boolean XMSHandle::Store( void far *a, unsigned& len, long off = -1 )
---------------------------------------------------------------------
This function stores the data in 'a' into the EMB.  The store begins
at 'off' (the -1 default value writes from the current 'Index'), and
'len' bytes are written into the EMB.  'len' may be changed to
reflect the actual number of bytes written.  The function returns
True on success, or False if there was an error.

void XMSHandle::Store( char& a )
void XMSHandle::Store( long& a )
void XMSHandle::Store( int&  a )
--------------------------------
These three functions operate similarly to their Get counterparts. 
They store the value found at 'a' into the EMB, starting at the
current Index pointer.  There is no return value.

*****Cache functions
boolean XMSHandle::CRealloc( unsigned size )
--------------------------------------------
CRealloc reallocates the size of the cache.  It returns True if the
cache was successfully set to 'size' bytes, False otherwise.

boolean XMSHandle::GetCache( void )
-----------------------------------
Use this function to determine whether or not the cache is currently
on.  It returns True if it's on, False otherwise.

void XMSHandle::SetCache( boolean s )
-------------------------------------
Use this function to set the state of the cache.  Set s = True to
enable cache operations, or s = False to disable the cache.

boolean XMSHandle::CBounds( long i )
------------------------------------
This function verifies that 'i' is within the cache boundaries. 
Typically, it is used internally to decide whether or not to update
                                - 13 -
the cache.  It returns True if the byte at 'i' is in the cache, or
False otherwise.

void XMSHandle::CacheRead( void )
---------------------------------
This function reloads the cache.  Beginning with the data at 'Index',
it will read in the next few sequential bytes until it is able to
fill the cache entirely.

void XMSHandle::CacheWrite( void )
----------------------------------
This function dumps the contents of the current cache and reloads
extended memory.  Data is never directly written to the extended
memory block until an explicit CacheWrite has been performed
(although all the internal functions like Store, Get, SetPtr, IncPtr,
etc. will properly update the cache, thus you should never actually
need to flush the cache buffer manually).

void XMSHandle::CacheUpdate( unsigned chksize=1 )
-------------------------------------------------
This function checks for 'chksize' bytes to see if they are in the
cache.  If the requested number of bytes ('chksize') beginning at 
the index pointer 'Index' cannot be found within the cache, it will
load that data into the cache.  Otherwise, it does nothing.

********* Writing your own Get/Store routines
I've tried to make it as simple as possible for the average
"joe-programmer" to use write his/her own Get/Store routines. 
Writing your own Get/Store procedures is very simple - I have
included the source (XMSHRW.CPP) for the XMSHandle Get/Store
routines.

Most of the details are in the source, and you can follow the
examples given.  Basically, you must make sure that the cache is
updated, and that you retrieve data from the cache if it's on.  Other
wise, you need to do a direct read from extended memory, calling the
xmsptr->EMBMoveTo... whatever routine.  You also need to check to see
whether the WHOLE data item can be found within the cache, especially
on the write routines.  I have dropped the checking for the Get
routines - you may decide that they should be in.

The CacheUpdate command will take care of doing most of the checking. 
You can specify a '1' in the parameter - this tells the cache you are
ABSOLUTELY POSITIVE the cache needs to be updated.  This way, you
avoid the double checking and save a little time.

********************* The End (almost) *****************************
I worked pretty hard on this, and it's still far from perfect (what a
bummer, dude).  I released it in it's "far from perfect" state
because I wanted to see if it was worth all the time I spent.  This
is why it's very important that I get some feedback from you guys. 
It's also a way for me to assess whether or not it's worth it for me
to program all kinds of apps and libraries for other people.  It's
hard to find friends who do this kind of system level programming, so
I'm on my own on most of this stuff.

						- Management


                        - Last (14) -
