                       ACCESSING EXTENDED MEMORY

                            Matthew Probert
                           Servile  Software


Information in this info journal has been adapted from information 
provided into the public domain by Borland.

Extended memory has all but taken over from Expanded Memory now 
(1996). It is faster and more useable than expanded memory. As with 
Expanded memory, Extended memory cannot be directly accessed through 
the standard DOS mode, and so a transfer buffer in conventional or 
"real-mode" memory needs to be used. The process to write data to 
Extended memory then involves copying the data to the transfer buffer 
in conventional memory, and from there copying it to Extended memory. 

Before any use may be made of Extended memory, a program should test 
to see if Extended memory is available. The following function, 
XMS_init(), tests for the presence of Extended memory, and if 
available calls another function, GetXMSEntry()  to initialise the 
program for using Extended Memory. The function also allocates a 
conventional memory transfer buffer. 


     
     /*
      BLOCKSIZE will be the size of our real-memory buffer that
      we'll swap XMS through (must be a multiple of 1024, since
      XMS is allocated in 1K chunks.)
     */
     
     #ifdef __SMALL__
     #define BLOCKSIZE (16L * 1024L)
     #endif
     
     
     #ifdef __MEDIUM__
     #define BLOCKSIZE (16L * 1024L)
     #endif
     
     
     #ifdef __COMPACT__
     #define BLOCKSIZE (64L * 1024L)
     #endif
     
     #ifdef __LARGE__
     #define BLOCKSIZE (64L * 1024L)
     #endif
     
     
     char XMS_init()
     {
      /*
           returns 0 if XMS present,
            1 if XMS absent
            2 if unable to allocate conventional memory transfer
              buffer
      */
      unsigned char status;
      _AX=0x4300;
      geninterrupt(0x2F);
      status = _AL;
      if(status==0x80)
      {
           GetXMSEntry();
           XMSBuf = (char far *) farmalloc(BLOCKSIZE);
           if (XMSBuf == NULL)
            return 2;
           return 0;
      }
      return 1;
     }
     
     void GetXMSEntry(void)
     {
      /*
           GetXMSEntry sets XMSFunc to the XMS Manager entry point
           so we can call it later
      */
     
      _AX=0x4310;
      geninterrupt(0x2F);
      XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);
     }


Once the presence of Extended memory has been confirmed, a program can 
find out how much Extended memory is available; 

     void XMSSize(int *kbAvail, int *largestAvail)
     {
      /*
           XMSSize returns the total kilobytes available, and the
           size in kilobytes of the largest available block
      */
     
      _AH=8;
      (*XMSFunc)();
      *largestAvail=_DX;
      *kbAvail=_AX;
     }


The following function may be called to allocate a block of Extended 
memory, like you would allocate a block of conventional memory. 

     char AllocXMS(unsigned long numberBytes)
     {
      /*
           Allocate a block of XMS memory numberBytes long
           Returns 1 on success
            0 on failure
      */
     
      _DX = (int)(numberBytes / 1024);
      _AH = 9;
      (*XMSFunc)();
      if (_AX==0)
      {
           return 0;
      }
      XMSHandle=_DX;
      return 1;
     }
     

Allocated Extended memory is not freed by DOS. A program using 
Extended memory must release it before terminating. This function 
frees a block of extended memory previously allocated by AllocXMS. 
Note, XMSHandle is a global variable of type int. 

     void XMS_free(void)
     {
      /*
           Free used XMS
      */
      _DX=XMSHandle;
      _AH=0x0A;
      (*XMSFunc)();
     }


Two functions are now given. One for writing data to Extended memory, 
and one for reading data from Extended memory into conventional 
memory. 

     /*
      XMSParms is a structure for copying information to and from
      real-mode memory to XMS memory
     */
     
     struct parmstruct
     {
      /*
           blocklength is the size in bytes of block to copy
      */
      unsigned long blockLength;
     
      /*
           sourceHandle is the XMS handle of source; 0 means that
           sourcePtr will be a 16:16 real-mode pointer, otherwise
           sourcePtr is a 32-bit offset from the beginning of the
           XMS area that sourceHandle points to
      */
     
      unsigned int sourceHandle;
      far void *sourcePtr;
     
      /*
           destHandle is the XMS handle of destination; 0 means that
           destPtr will be a 16:16 real-mode pointer, otherwise
           destPtr is a 32-bit offset from the beginning of the XMS
           area that destHandle points to
      */
     
      unsigned int destHandle;
      far void *destPtr;
     }
     XMSParms;
     
     
     char XMS_write(unsigned long loc, char far *val, unsigned length)
     {
      /*
           Round length up to next even value
      */
      length += length % 2;
     
      XMSParms.sourceHandle=0;
      XMSParms.sourcePtr=val;
      XMSParms.destHandle=XMSHandle;
      XMSParms.destPtr=(void far *) (loc);
      XMSParms.blockLength=length;  /* Must be an even number! */
      _SI = FP_OFF(&XMSParms);
      _AH=0x0B;
      (*XMSFunc)();
      if (_AX==0)
      {
           return 0;
      }
      return 1;
     }
     
     
     void *XMS_read(unsigned long loc,unsigned length)
     {
      /*
           Returns pointer to data
           or NULL on error
      */
     
      /*
           Round length up to next even value
      */
      length += length % 2;
     
      XMSParms.sourceHandle=XMSHandle;
      XMSParms.sourcePtr=(void far *) (loc);
      XMSParms.destHandle=0;
      XMSParms.destPtr=XMSBuf;
      XMSParms.blockLength=length;       /* Must be an even number */
      _SI=FP_OFF(&XMSParms);
      _AH=0x0B;
      (*XMSFunc)();
      if (_AX==0)
      {
           return NULL;
      }
      return XMSBuf;
     }
     
     
And now putting it all together is a demonstration program.

     /* A sequential table of variable length records in XMS */
     
     #include <dos.h>
     #include <stdio.h>
     #include <stdlib.h>
     #include <alloc.h>
     #include <string.h>
     
     #define TRUE 1
     #define FALSE 0
     
     /*
      BLOCKSIZE will be the size of our real-memory buffer that
      we'll swap XMS through (must be a multiple of 1024, since
      XMS is allocated in 1K chunks.)
     */
     
     #ifdef __SMALL__
     #define BLOCKSIZE (16L * 1024L)
     #endif
     
     
     #ifdef __MEDIUM__
     #define BLOCKSIZE (16L * 1024L)
     #endif
     
     
     #ifdef __COMPACT__
     #define BLOCKSIZE (64L * 1024L)
     #endif
     
     #ifdef __LARGE__
     #define BLOCKSIZE (64L * 1024L)
     #endif
     
     
     /*
      XMSParms is a structure for copying information to and from
      real-mode memory to XMS memory
     */
     
     struct parmstruct
     {
      /*
           blocklength is the size in bytes of block to copy
      */
      unsigned long blockLength;
     
      /*
           sourceHandle is the XMS handle of source; 0 means that
           sourcePtr will be a 16:16 real-mode pointer, otherwise
           sourcePtr is a 32-bit offset from the beginning of the
           XMS area that sourceHandle points to
      */
     
      unsigned int sourceHandle;
      far void *sourcePtr;
     
      /*
           destHandle is the XMS handle of destination; 0 means that
           destPtr will be a 16:16 real-mode pointer, otherwise
           destPtr is a 32-bit offset from the beginning of the XMS
           area that destHandle points to
      */
     
      unsigned int destHandle;
      far void *destPtr;
     }
     XMSParms;
     
     void far (*XMSFunc) (void);   /* Used to call XMS manager
     (himem.sys) */
     char GetBuf(void);
     void GetXMSEntry(void);
     
     char *XMSBuf;  /* Conventional memory buffer for transfers */
     
     unsigned int XMSHandle;  /* handle to allocated XMS block */
     
     
     char XMS_init()
     {
      /*
           returns 0 if XMS present,
            1 if XMS absent
            2 if unable to allocate transfer buffer
      */
      unsigned char status;
      _AX=0x4300;
      geninterrupt(0x2F);
      status = _AL;
      if(status==0x80)
      {
           GetXMSEntry();
           XMSBuf = (char far *) farmalloc(BLOCKSIZE);
           if (XMSBuf == NULL)
            return 2;
           return 0;
      }
      return 1;
     }
     
     void GetXMSEntry(void)
     {
      /*
           GetXMSEntry sets XMSFunc to the XMS Manager entry point
           so we can call it later
      */
     
      _AX=0x4310;
      geninterrupt(0x2F);
      XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);
     }
     
     
     void XMSSize(int *kbAvail, int *largestAvail)
     {
      /*
           XMSSize returns the total kilobytes available, and the
     size
           in kilobytes of the largest available block
      */
     
      _AH=8;
      (*XMSFunc)();
      *largestAvail=_DX;
      *kbAvail=_AX;
     }
     
     char AllocXMS(unsigned long numberBytes)
     {
      /*
           Allocate a block of XMS memory numberBytes long
      */
     
      _DX = (int)(numberBytes / 1024);
      _AH = 9;
      (*XMSFunc)();
      if (_AX==0)
      {
           return FALSE;
      }
      XMSHandle=_DX;
      return TRUE;
     }
     
     void XMS_free(void)
     {
      /*
           Free used XMS
      */
      _DX=XMSHandle;
      _AH=0x0A;
      (*XMSFunc)();
     }
     
     char XMS_write(unsigned long loc, char far *val, unsigned length)
     {
      /*
           Round length up to next even value
      */
      length += length % 2;
     
      XMSParms.sourceHandle=0;
      XMSParms.sourcePtr=val;
      XMSParms.destHandle=XMSHandle;
      XMSParms.destPtr=(void far *) (loc);
      XMSParms.blockLength=length;  /* Must be an even number! */
      _SI = FP_OFF(&XMSParms);
      _AH=0x0B;
      (*XMSFunc)();
      if (_AX==0)
      {
           return FALSE;
      }
      return TRUE;
     }
     
     
     void *XMS_read(unsigned long loc,unsigned length)
     {
      /*
           Returns pointer to data
           or NULL on error
      */
     
      /*
           Round length up to next even value
      */
      length += length % 2;
     
      XMSParms.sourceHandle=XMSHandle;
      XMSParms.sourcePtr=(void far *) (loc);
      XMSParms.destHandle=0;
      XMSParms.destPtr=XMSBuf;
      XMSParms.blockLength=length;  /* Must be an even number */
      _SI=FP_OFF(&XMSParms);
      _AH=0x0B;
      (*XMSFunc)();
      if (_AX==0)
      {
           return NULL;
      }
      return XMSBuf;
    }
     
     
     /*
      Demonstration code
      Read various length strings into a single XMS block (EMB)
      and write them out again
     */
     
     int main()
     {
      int kbAvail,largestAvail;
      char buffer[80];
      char *p;
      long pos;
      long end;
     
      if (XMS_init() == 0)
           printf("XMS Available ...\n");
      else
      {
           printf("XMS Not Available\n");
           return(1);
      }
     
      XMSSize(&kbAvail,&largestAvail);
      printf("Kilobytes Available: %d; Largest block:
     %dK\n",kbAvail,largestAvail);
     
      if (!AllocXMS(2000 * 1024L))
           return(1);
     
     
      pos = 0;
     
      do
      {
           p = fgets(buffer,1000,stdin);
           if (p != NULL)
           {
                XMS_write(pos,buffer,strlen(buffer) + 1);
                pos += strlen(buffer) + 1;
           }
      }
      while(p != NULL);
     
      end = pos;
     
      pos = 0;
     
      do
      {
           memcpy(buffer,XMS_read(pos,100),70);
           printf("%s",buffer);
           pos += strlen(buffer) + 1;
      }
      while(pos < end);
     
      /*
           It is VERY important to free any XMS before exiting!
      */
      XMS_free();
      return 0;
     }

