{---------------- Extended Memory Access Unit -----------------}
UNIT XMS;
(**) INTERFACE (**)
VAR
  XMSErrorCode : byte;     { Error Code - Defined in XMS Spec }
  XMSAddr      : Pointer;  { Entry Point for HIMEM.SYS Driver }

FUNCTION XMSDriverLoaded: Boolean;
FUNCTION XMSTotalMemoryAvailable: Word;
FUNCTION XMSLargestBlockAvailable: Word;
FUNCTION XMSAllocateBlock(KBSize: Word): Word;
FUNCTION XMSReleaseBlock(Handle: Word): Boolean;
FUNCTION XMSMoveDataTo(SourceAddr: Pointer; NumBytes: LongInt;
           XMSHandle: Word; XMSOffset: LongInt): Boolean;
FUNCTION XMSGetDataFrom(XMSHandle: Word; XMSOffset: LongInt;
           NumBytes: LongInt; LowMemAddr: Pointer): Boolean;

(**) IMPLEMENTATION (**)

TYPE
  XMSMoveStruct = record
    movelen   : LongInt;   { length of block to move in bytes }
    case integer of
      { Case 0 Variant for Low Memory to XMS }
      0: (SHandle   : Word;      { source handle = 0
                                   for conventional memory }
          SPtr      : pointer;   { source address }
          XMSHdl    : Word;      { XMS destination handle }
          XMSOffset : LongInt);  { 32 bit XMS offset }
      { Case 1 Variant for XMS to Low Memory }
      1: (XMSH      : Word;      { XMS source handle }
          XMSOfs    : LongInt;   { starting offset in XMS}
          DHandle   : Word;      { 0 when conventional memory
                                   destination }
          DPtr      : pointer);  { address in conventional memory }
    END;

VAR moveparms : XMSMoveStruct;   { structure for moving to and
                                   from XMS }

{**************************************************************}
{ XMSDriverLoaded - Returns true IF Extended Memory Driver     }
{                   HIMEM.SYS Loaded                           }
{                 - Sets Entry Point Address - XMSAddr         }
{**************************************************************}
FUNCTION XMSDriverLoaded: Boolean;
CONST
  himemseg: Word = 0;
  himemofs: Word = 0;
BEGIN
  XMSErrorCode := 0;
  ASM
    mov ax,4300h      { Check to see IF HIMEM.SYS installed }
    int 2fh
    cmp al,80h        { Returns AL = 80H IF installed }
    jne @1
    mov ax,4310h      { Now get the entry point }
    int 2fh
    mov himemofs,bx
    mov himemseg,es
  @1:
  END;
  XMSDriverLoaded := (himemseg <> 0);
  XMSAddr := Ptr(himemseg,himemofs);
END;

{**************************************************************}
{ XMSTotalMemoryAvailable - Returns Total XMS Memory Available }
{**************************************************************}
FUNCTION XMSTotalMemoryAvailable: Word;
BEGIN
  XMSErrorCode := 0;
  XMSTotalMemoryAvailable := 0;
  IF XMSAddr = nil THEN        { Check IF HIMEM.SYS Loaded }
    IF NOT XMSDriverLoaded THEN exit;
  ASM
    mov  ah,8
    call XMSAddr
    or   ax,ax
    jnz  @1
    mov  XMSErrorCode,bl  { Set Error Code }
    xor  dx,dx
    @1:
    mov  @Result,dx       { DX = total free extended memory }
  END;
END;

{**************************************************************}
{ XMSLargestBlockAvailable - Returns Largest Contiguous        }
{                            XMS Block Available               }
{**************************************************************}
FUNCTION XMSLargestBlockAvailable: Word;
BEGIN
  XMSErrorCode := 0;
  XMSLargestBlockAvailable := 0;
  IF XMSAddr = nil THEN         { Check IF HIMEM.SYS Loaded }
    IF NOT XMSDriverLoaded THEN exit;
  ASM
    mov  ah,8
    call XMSAddr
    or   ax,ax
    jnz  @1
    mov  XMSErrorCode,bl { On Error, Set Error Code }
    @1:
    mov  @Result,ax      { AX=largest free XMS block }
  END;
END;

{***************************************************************}
{ XMSAllocateBlock - Allocates Block of XMS Memory              }
{                  - Input - KBSize: No of Kilobytes requested  }
{                  - Returns Handle for memory IF successful    }
{***************************************************************}
FUNCTION XMSAllocateBlock(KBSize: Word): Word;
BEGIN
  XMSAllocateBlock := 0;
  XMSErrorCode := 0;
  IF XMSAddr = nil THEN { Check IF HIMEM.SYS Loaded }
    IF NOT XMSDriverLoaded THEN exit;
  ASM
    mov  ah,9
    mov  dx,KBSize
    call XMSAddr
    or   ax,ax
    jnz  @1
    mov  XMSErrorCode,bl { On Error, Set Error Code }
    xor  dx,dx
    @1:
    mov  @Result,dx      { DX = handle for extended memory }
  END;
END;

{**************************************************************}
{ XMSReleaseBlock - Releases Block of XMS Memory               }
{                 - Input:   Handle identifying memory to be   }
{                   released                                   }
{                 - Returns  true IF successful                }
{**************************************************************}
FUNCTION XMSReleaseBlock(Handle: Word): Boolean;
VAR OK : Word;
BEGIN
  XMSErrorCode := 0;
  XMSReleaseBlock := false;
  IF XMSAddr = nil THEN   { Check IF HIMEM.SYS Loaded }
    IF NOT XMSDriverLoaded THEN exit;
  ASM
    mov  ah,0Ah
    mov  dx,Handle
    call XMSAddr
    or   ax,ax
    jnz  @1
    mov  XMSErrorCode,bl  { On Error, Set Error Code }
    @1:
    mov  OK,ax
  END;
  XMSReleaseBlock := (OK <> 0);
END;

{**************************************************************}
{ XMSMoveDataTo - Moves Block of Data from Conventional        }
{                 Memory to XMS Memory                         }
{               - Data Must have been previously allocated     }
{               - Input - SourceAddr : address of data in      }
{                                      conventional memory     }
{                       - NumBytes   : number of bytes to move }
{                       - XMSHandle  : handle of XMS block     }
{                       - XMSOffset  : 32 bit destination      }
{                                      offset in XMS block     }
{               - Returns true IF completed successfully       }
{**************************************************************}
FUNCTION XMSMoveDataTo(SourceAddr: Pointer; NumBytes: LongInt;
           XMSHandle: Word; XMSOffset: LongInt): Boolean;
VAR Status    : Word;
BEGIN
  XMSErrorCode := 0;
  XMSMoveDataTo := false;
  IF XMSAddr = nil THEN  { Check IF HIMEM.SYS Loaded }
    IF NOT XMSDriverLoaded THEN exit;
  MoveParms.MoveLen   := NumBytes;
  MoveParms.SHandle   := 0;         { Source Handle=0 For
                                      Conventional Memory}
  MoveParms.SPtr      := SourceAddr;
  MoveParms.XMSHdl    := XMSHandle;
  MoveParms.XMSOffset := XMSOffset;
  ASM
    mov  ah,0Bh
    mov  si,offset MoveParms
    call XMSAddr
    mov  Status,ax       { Completion Status }
    or   ax,ax
    jnz  @1
    mov  XMSErrorCode,bl { Save Error Code }
    @1:
  END;
  XMSMoveDataTo := (Status <> 0);
END;

{**************************************************************}
{ XMSGetDataFrom - Moves Block From XMS to Conventional Memory }
{                - Data Must have been previously allocated    }
{                  and moved to XMS                            }
{               - Input - XMSHandle  : handle of source        }
{                                      XMS block               }
{                       - XMSOffset  : 32 bit source offset    }
{                                      in XMS block            }
{                       - NumBytes   : number of bytes to move }
{                       - LowMemAddr : destination addr in     }
{                                      conventional memory     }
{               - Returns true IF completed successfully       }
{**************************************************************}
FUNCTION XMSGetDataFrom(XMSHandle: Word; XMSOffset: LongInt;
           NumBytes: LongInt; LowMemAddr: Pointer): Boolean;
VAR Status    : Word;
BEGIN
  XMSErrorCode := 0;
  XMSGetDataFrom := false;
  IF XMSAddr = nil THEN   { Check IF HIMEM.SYS Loaded }
    IF NOT XMSDriverLoaded THEN exit;
  MoveParms.MoveLen := NumBytes;  { Set-Up Structure to Pass }
  MoveParms.XMSh    := XMSHandle;
  MoveParms.XMSOfs  := XMSOffset;
  MoveParms.DHandle := 0;         { Dest Handle=0 For
                                    Conventional Memory}
  MoveParms.DPtr    := LowMemAddr;
  ASM
    mov  ah,0Bh
    mov  si,offset MoveParms
    call XMSAddr
    mov  Status,ax       { Completion Status }
    or   ax,ax
    jnz  @1
    mov  XMSErrorCode,bl { Set Error Code }
    @1:
  END;
  XMSGetDataFrom := (Status <> 0);
END;

BEGIN
  XMSAddr      := nil; { Initialize XMSAddr }
  XMSErrorCode := 0;
END.

{ *********************************************************************** }
{ *********************************************************************** }
{ *********************************************************************** }
{                                XMS DEMO PROGRAM                         }

{$X+}
Program XMSTest;
USES crt, XMS;
CONST
  NumVars     = 131072;   { 131072 total no of variables in array }
  BytesPerVar = 4;        { ie. 2 for integers, 4 for LongInts ...}
VAR
  I       : LongInt;
  Result  : LongInt;
  Hdl     : Word;          { Handle for Extended memory allocated }
  HiMemOK : boolean;
BEGIN
  ClrScr;
  HiMemOK := XMSDriverLoaded;
  WriteLn('HIMEM.SYS Driver Loaded=', HiMemOK);
  IF NOT HiMemOK THEN Halt;
  WriteLn('Total Extended Memory: ', XMSTotalMemoryAvailable, ' KB');
  WriteLn('Largest Free Extended Memory Block: ',
           XMSLargestBlockAvailable, ' KB');

  {Allocate Memory - Hdl is memory block handle or identifier}
  Hdl := XMSAllocateBlock((NumVars * BytesPerVar + 1023) DIV 1024);
         {1023 to Round Up to next KB}
  WriteLn((NumVars * BytesPerVar + 1023) DIV 1024,'KB Handle=',Hdl);
  WriteLn('Total Extended Memory Available After Allocation: ',
          XMSTotalMemoryAvailable, ' KB');

  { Fill the variables with 1 ... NumVars for exercise }
  WriteLn('Filling Memory Block ');
  FOR I := 1 TO NumVars DO
    BEGIN
      { parameters in Move Data are - Address of Data to Move
                                    - Number of Bytes
                                    - Memory Handle
                                    - Offset in XMS Area }
      IF NOT XMSMoveDataTo(@I, BytesPerVar, Hdl, (I - 1) *
        BytesPerVar) THEN
        WriteLn('Error on Move to XMS: ',I,' Error: ', XMSErrorCode);
      IF I MOD 1024 = 0 THEN Write(I:6,^M);
    END;
  WriteLn;
  { Now read a couple of locations just to show how}
  I := 1;  { First Element }
  IF NOT XMSGetDataFrom(Hdl, (I - 1) * BytesPerVar,
    BytesPerVar, @Result) THEN
    WriteLn('Error on XMSGetDataFrom')
    ELSE WriteLn('XMS Data [',I,']=',Result); { Print it }
  I := NumVars;  { Last Element }
  IF NOT XMSGetDataFrom(Hdl, (I - 1) * BytesPerVar, BytesPerVar,
    @Result) THEN
    WriteLn('Error on XMSGetDataFrom')
    ELSE WriteLn('XMS Data [',I,']=',Result); { Print it }

  WriteLn('Release status=', XMSReleaseBlock(Hdl));
  WriteLn('Press a key.');
  ReadKey;
END.
