{*************************** EMS.PAS ************************
*** Demonstrate calls to Extended memory manager          ***
************************************************************}

USES

  Crt,Dos;

{***************************************************************}

TYPE
  PtrType = RECORD                  {Define a pointer record    }
    Offset  : Word;                 {  type so we can access the}
    Segment : Word                  {  individual pointer fields}
    END;
  DeviceName = ARRAY[1..8] OF Char; {Defined to test device Name}

CONST

  EmsInt      = $67;                {EMS Interrupt number       }
  IOCtlFunc   = $44;                {IOCtl DOS Function number  }
  PageLen     = 16384;              {Length of memory page      }
  MsgLen      = 16;                 {Message len plus len byte  }
  MsgsPerPage = PageLen DIV MsgLen; {Number of messages in page }
  NumMsgs     = 5000;               {Number EMS messages        }

  {*** Emm functions ***}

  GetStatus        = $40;
  GetPageFrameAddr = $41;
  GetUnallocPages  = $42;
  GetEmmVersion    = $46;
  AllocatePages    = $43;
  MapHandlePage    = $44;
  DeallocatePages  = $45;

VAR
  P0, P1, P2, P3 : Pointer;     {Pointers to physical pages     }
  EmmHandle      : Integer;     {Handle for EMM allocated pages }
  Tmp            : FILE;        {Temp file to test if EMM exists}
  MsgBuf         : Pointer;     {Pointer into mapped memory     }
  Buff           : String[15];  {Buffer for msg stored in EM    }
  I              : Integer;     {Dummy variable                 }
  EmmRegs        : Registers;   {Registers for interrupt calls  }
  Page,Index     : Integer;     {Used to address page frame     }
  EmsVector      : Pointer;     {EMM address from Int $67       }
  StrNum         : String[4];   {Holds record # for EMM msg     }

{******** Function to convert word value to Hex string *********}

FUNCTION Hex(IntNbr : Word): String;
CONST
  HexDigit :ARRAY[0..15] OF Char = '0123456789ABCDEF';
VAR
  S        : String[2];         {Temporary String}
  TempByte : Byte;
BEGIN
  TempByte := Hi(IntNbr);                  {Convert upper nibble}
  S := HexDigit[TempByte DIV 16] +
       HexDigit[TempByte MOD 16];
  TempByte := Lo(IntNbr);                  {Convert lower nibble}
  Hex := S + HexDigit[TempByte DIV 16] +
             HexDigit[TempByte MOD 16];
END;

{******** Create a string that contains a pointer value ********}

FUNCTION PrintPointer(P : Pointer): String;

BEGIN
  PrintPointer := Hex(PtrType(P).Segment) + ':' +
                  Hex(PtrType(P).Offset);
  END;

{*********** Print the EMM Status to the screen ****************}

PROCEDURE EmmPrintStatus(Status: Byte);

CONST
  EmmStatus : ARRAY [$80..$8F] OF String =
    ('Driver malfunction',
     'Hardware malfunction',
     '',
     'Bad Handle',
     'Undefined FUNCTION',
     'No free handles',
     'Page map context Error',
     'Insufficient memory pages',
     'Not enough free pages',
     'Can''t allocate zero (0) pages',
     'Logical page out of range',
     'Physical page out of range',
     'Page map hardware RAM full',
     'Page map already has a Handle',
     'Page map not mapped to Handle',
     'Undefined subfunction number');

BEGIN
  CASE Status OF
    0        : WriteLn('Ok');
    $80..$8F : WriteLn('EMM: ',EmmStatus[Status])
    ELSE WriteLn('EMM: Unknown status = $',Hex(Status))
    END
END;



{******** Generic procedure to call the EMM interrupt **********}

PROCEDURE CallEmm(EmmFunction : Byte; VAR R : Registers);

BEGIN
  R.AH := EmmFunction;
  Intr(EmsInt,R);
  IF (R.AH <> 0) THEN BEGIN
    EmmPrintStatus(EmmRegs.AH);
    Halt(EmmRegs.AH)
    END
  END;

{******************  Main Program  *****************************}


BEGIN

  ClrScr;

{$DEFINE CheckFile}              {Undefine to test second method}

{$IFDEF CheckFile}                  {Check EMM driver - Method 1}

  GetIntVec(EmsInt,EmsVector);
  PtrType(EmsVector).Offset := 10;
  IF (DeviceName(EmsVector^) <> 'EMMXXXX0') THEN BEGIN
    WriteLn('No EMM driver present');
    Halt(1)
    END;

{$ELSE}                             {Check EMM driver - Method 2}

  {***** Determine if EMM is installed by opening EMMXXXX0 *****}

  {$I-}
  Assign(Tmp,'EMMXXXX0');
  Reset(Tmp);
  {$I+}
  IF (IOResult <> 0) THEN BEGIN      {Opened file without error?}
    WriteLn('No EMM driver present');
    WriteLn('IO error #',IOResult:3);
    Halt(1)
    END;

  EmmRegs.AH := IOCtlFun              {Call IOCtl function to   }
  EmmRegs.AL := $00;                  { test whether EMMXXXX0 is}
  EmmRegs.BX := FileRec(Tmp).Handle;  { a file or a device      }

  MsDos(EmmRegs);
  Close(Tmp);

  IF (EmmRegs.Flags AND 1) = 0 THEN            {Call successfull}
    IF (EmmRegs.DX AND $80) = $80 THEN   {Handle is for a device}
      WriteLn('Handle refers to a device')
    ELSE BEGIN
      WriteLn('Handle refers to a FILE');
      WriteLn('Unable to contact EMM driver if present');
      Halt(1)
      END
  ELSE BEGIN                                 {Call unsuccessfull}
    CASE EmmRegs.AX OF
      1 : WriteLn('Invalid IOCTL subfunction');
      5 : WriteLn('Access to IOCTL denied');
      6 : WriteLn('Invalid Handle')
      ELSE WriteLn('Unknown error # ',Hex(EmmRegs.AX))
      END;
    WriteLn('Unable to contact EMM driver');
    Halt(1)
    END;


{$ENDIF}

  WriteLn('EMM driver present');

  {********  Print the current status of the EMM driver ********}

  CallEmm(GetStatus,EmmRegs);
  WriteLn('EMM Status Ok');

  {******** Print the version number of EMM driver *************}

  CallEmm(GetEmmVersion,EmmRegs);

  WriteLn('EMS driver version = ',
           (EmmRegs.AL SHR 4):1,'.',
           (EmmRegs.AL AND $0F):1);

  IF EmmRegs.AL < $32 THEN BEGIN
    WriteLn('Error - EMM is version is earlier than 3.2');
    Halt(1)
    END;

  {***** Print the page frame & physical window addresses ******}

  CallEmm(GetPageFrameAddr,EmmRegs);

  PtrType(P0).Segment := EmmRegs.BX; { Window 0 -> P0 = BX:0000 }
  PtrType(P0).Offset := $0;
  PtrType(P1).Segment := EmmRegs.BX; { Window 1 -> P1 = BX:4000 }
  PtrType(P1).Offset := $4000;
  PtrType(P2).Segment := EmmRegs.BX; { Window 2 -> P2 = BX:8000 }
  PtrType(P2).Offset := $8000;
  PtrType(P3).Segment := EmmRegs.BX; { Window 3 -> P3 = BX:C000 }
  PtrType(P3).Offset := $C000;

  WriteLn('Page frame segment address = ',Hex(EmmRegs.BX));
  WriteLn('Physical page 0 address = ',PrintPointer(P0));
  WriteLn('Physical page 1 address = ',PrintPointer(P1));
  WriteLn('Physical page 2 address = ',PrintPointer(P2));
  WriteLn('Physical page 3 address = ',PrintPointer(P3));

  {***** Print # of unallocated pages and total # of pages *****}

  CallEmm(GetUnallocPages,EmmRegs);
  WriteLn('Total EMS pages = ',EmmRegs.DX:4);
  WriteLn('Unused EMS pages = ',EmmRegs.BX:4);

  {***** Allocate some pages of expanded memory *****}

  EmmRegs.BX := (NumMsgs + MsgsPerPage) DIV MsgsPerPage;
  CallEmm(AllocatePages,EmmRegs);
  WriteLn('Allocated ',EmmRegs.BX,
          ' pages to handle # ',EmmRegs.DX);
  EmmHandle := EmmRegs.DX;

  {***** Load EMS RAM with data *****}

  MsgBuf := P0;               {* Set Message pointer to Page 0 *}

  FOR I := 0 TO NumMsgs-1 DO BEGIN
    Str(I:4,StrNum);                       {Create msg string   }
    Buff := ' EMS msg # ' + StrNum;
    IF (I MOD 100) = 0 THEN Write('.');    {Dsp status on screen}
    Page := I DIV MsgsPerPage;
    Index := I MOD MsgsPerPage;
    PtrType(MsgBuf).Offset := Index * SizeOf(Buff);

     {**** Map indicated logical page into physical page 0 ****}

    EmmRegs.AH := MapHandlePage;           {Map EMS page cmd    }
    EmmRegs.BX := Page;                    {Logical page number }
    EmmRegs.AL := 0;                       {Physical page 0     }
    EmmRegs.DX := EmmHandle;               {EMM RAM handle      }

    Intr(EmsInt,EmmRegs);

    IF EmmRegs.AH = 0 THEN
      Move(Buff[0],MsgBuf^,SizeOf(Buff))   {Set message into mem}
    ELSE BEGIN
      EmmPrintStatus(EmmRegs.AH);
      I := NumMsgs
      END
    END;

    WriteLn;

  {******  Allow user to access any message in the buffer ******}

  I := $FF;
  WHILE I <> -1 DO BEGIN
    MsgBuf := P3;                 {Set MsgBuf to physical page 3}
    Write('Enter message # to retrieve, or -1 to quit: ');
    ReadLn(I);
    IF (I >= 0) AND (I < NumMsgs) THEN BEGIN
      Page := I DIV MsgsPerPage;
      Index := I MOD MsgsPerPage;

        {**** Map indicated page into physical page 3 ****}

      EmmRegs.AH := MapHandlePage;         {Map EMM page command}
      EmmRegs.BX := Page;                  {Logical page number }
      EmmRegs.AL := 3;                     {Physical page 3     }
      EmmRegs.DX := EmmHandle;             {EMM RAM handle      }

      Intr(EmsInt,EmmRegs);

      IF EmmRegs.AH = 0 THEN BEGIN
        Inc(PtrType(MsgBuf).Offset,Index * SizeOf(Buff));
        Move(MsgBuf^,Buff[0],SizeOf(Buff));
        Write('Retrieved message -> ',Buff);
        WriteLn(' from page #',Page:2,' Index',Index:5);
        END
      ELSE BEGIN
        EmmPrintStatus(EmmRegs.AH);
        I := -1;
        END
      END
    END;

     {***** Free the EMS RAM back to the EMM driver ****}

  EmmRegs.DX := EmmHandle;
  CallEmm(DeallocatePages,EmmRegs);
  WriteLn('Released all memory for handle ',EmmRegs.DX:2);
END.
