{$A-,B-,D-,E-,F-,G-,I-,L-,N-,O-,R-,S+,V-,X-}

Unit UMB_Heap;

{----------------------------------------------------------------------------}

interface

  Procedure Extend_Heap;        { Use Upper Memory Blocks (UMB) to extend    }
                                { the Turbo Pascal 6.0 heap.  This procedure }
                                { should be called as soon as possible in    }
                                { your code.                                 }
  var
    UMB_Heap_Debug : Boolean;   { If true, releases UMBs immediately to make }
                                { sure they're available for the next run    }
                                { without rebooting.  Used when debugging in }
                                { the IDE.  If not used then, the UMBs may   }
                                { not get freed between executions.          }

{----------------------------------------------------------------------------}

implementation

const
  Max_Blocks = 4;              { It's not likely more than 4 UMBs are needed }

type
  PFreeRec = ^TFreeRec;      {  From pg. 216 of the TP6 programmer's guide.  }
  TFreeRec = record          {  It's used for traversing the free blocks of  }
    Next : PFreeRec;         {  the heap.                                    }
    Size : Pointer;
  end;

var
  XMS_Driver : Pointer;      {  Pointer to the XMS driver.  }
  Num_Blocks : Word;
  Block_Address,
  Block_Size : Array[1..Max_Blocks+1] of Pointer;
  SaveExitProc : Pointer;

{----------------------------------------------------------------------------}

{  Swap to pointers.  Needed when sorting the UMB addresses.  }

Procedure Pointer_Swap(var A,B : Pointer);
  var
    Temp : Pointer;
  Begin
    Temp := A;
    A := B;
    B := Temp;
  End;

{----------------------------------------------------------------------------}

Function XMS_Driver_Present : Boolean;  { XMS software present? }
  var
    Result : Boolean;
  Begin
    Result := False;      { Assume no XMS driver }
    asm
      @Begin:
        mov ax,4300h
        int 2Fh
        cmp al,80h
        jne @Fail
        mov ax,4310h
        int 2Fh
        mov word ptr XMS_Driver+2,es       { Get the XMS driver entry point }
        mov word ptr XMS_Driver,bx
        mov Result,1
        jmp @End
      @Fail:
        mov Result,0
      @End:
    end;
    XMS_Driver_Present := Result;
  End;

{----------------------------------------------------------------------------}

Procedure Allocate_UMB_Heap;         { Add the four largest UMBs to the heap }
  var
    i,j : Word;
    UMB_Strategy,
    DOS_Strategy,
    Segment,Size : Word;
    Get_Direct : Boolean;   { Get UMB direct from XMS if TRUE, else from DOS }
  Begin
    Num_Blocks := 0;

    for i := 1 to Max_Blocks do
      begin
        Block_Address[i] := nil;
        Block_Size[i] := nil;
      end;

    asm
      mov ax,5800h
      int 21h                     { Get and save the DOS allocation strategy }
      mov [DOS_Strategy],ax
      mov ax,5802h
      int 21h                     { Get and save the UMB allocation strategy }
      mov [UMB_Strategy],ax
      mov ax,5801h
      mov bx,0000h
      int 21h                      { Set the DOS allocation strategy so that }
      mov ax,5803h                 { it uses only high memory                }

                                   { DON'T TRUST THIS FUNCTION.  DOS WILL GO }
                                   { AHEAD AND TRY TO ALLOCATE LOWER MEMORY  }
                                   { EVEN AFTER YOU TELL IT NOT TO!          }
      mov bx,0001h
      int 21h                      { Set the UMB allocation strategy so that }
    end;                           { UMBs are added to the DOS mem chain     }

    Get_Direct := True;            { Try to get UMBs directly from the XMS   }
                                   { if possible.                            }
    for i := 1 to Max_Blocks do
      begin
        Segment := 0;
        Size := 0;

        if Get_Direct then         { Get a UMB direct from the XMS driver.   }
          begin
            asm
              @Begin:
                mov ax,01000h         
                mov dx,0FFFFh         { Ask for the impossible to ...        }
                push ds               { Get the size of the next largest UMB }
                mov cx,ds
                mov es,cx
                call es:[XMS_Driver]
                cmp dx,100h           { Don't bother with anything < 1K      }
                jl @End
                mov ax,01000h
                call es:[XMS_Driver]  { Get the next largest UMB }
                cmp ax,1
                jne @End
                cmp bx,0A000h         { It better be above 640K }
                jl @End               { We can't trust DOS 5.00 }
                mov [Segment],bx
                mov [Size],dx
              @End:
                pop ds
            end;
            if ((i = 1) and (Size = 0)) then  { if we couldn't get the UMB  }
              Get_Direct := False;            { from the XMS driver, don't  }
          end;                                { try again the next time.    }

        if (not Get_Direct) then   { Get a UMB via DOS }
          begin
            asm
              @Begin:
                mov ax,4800h
                mov bx,0FFFFh         { Ask for the impossible to ...        }
                int 21h               { Get the size of the next largest UMB }
                cmp bx,100h           { Don't bother with anything < 1K      }
                jl @End
                mov ax,4800h
                int 21h               { Get the next largest UMB }
                jc @End
                cmp ax,0A000h         { It better be above 640K }
                jl @End               { We can't trust DOS 5.00 }
                mov [Segment],ax
                mov [Size],bx
              @End:
            end;
          end;

        if (Segment > 0) then                      { Did it work? }
          begin
            Block_Address[i] := Ptr(Segment,0);
            Inc(Num_Blocks);
          end;
        Block_Size[i] := Ptr(Size,0);
      end;
    if (Num_Blocks > 0) then               { Sort the UMB addrs in ASC order }
      begin
        for i := 1 to Num_Blocks-1 do
          for j := i+1 to Num_Blocks do
            if (Seg(Block_Address[i]^) > Seg(Block_Address[j]^)) then
              begin
                Pointer_Swap(Block_Address[i],Block_Address[j]);
                Pointer_Swap(Block_Size[i],Block_Size[j]);
              end;
      end;
    asm
      mov ax,5803h
      mov bx,[UMB_Strategy]
      int 21h                          { Restore the UMB allocation strategy }
      mov ax,5801h
      mov bx,[DOS_Strategy]
      int 21h                          { Restore the DOS allocation strategy }
    end;
  End;

{----------------------------------------------------------------------------}

Procedure Release_UMB; far;                 { Exit procedure to release UMBs }
  var
    i : Word;
    Segment : Word;
  Begin
    ExitProc := SaveExitProc;
    if (Num_Blocks > 0) then
      begin
        asm
          mov ax,5803h
          mov bx,0000h
          int 21h                       { Set the UMB status to release UMBs }
        end;
        for i := 1 to Num_Blocks do
          begin
            Segment := Seg(Block_Address[i]^);
            if (Segment > 0) then
              asm
                mov ax,$4901
                mov bx,[Segment]
                mov es,bx
                int 21h                                    { Release the UMB }
              end;
          end;
      end;
  End;

{----------------------------------------------------------------------------}

Procedure Extend_Heap;
  var
    i : Word;
    Temp : PFreeRec;
  Begin
    if XMS_Driver_Present then
      begin
        Allocate_UMB_Heap;
        if UMB_Heap_Debug then
          Release_UMB;
        if (Num_Blocks > 0) then
          begin                             { Attach UMBs to the FreeList    }
            for i := 1 to Num_Blocks do
              PFreeRec(Block_Address[i])^.Size := Block_Size[i];
            for i := 1 to Num_Blocks do
              PFreeRec(Block_Address[i])^.Next := Block_Address[i+1];

            PFreeRec(Block_Address[Num_Blocks])^.Next := nil;

            if (FreeList = HeapPtr) then
              with PFreeRec(FreeList)^ do
                begin
                  Next := Block_Address[1];
                  Size := Ptr(Seg(HeapEnd^)-Seg(HeapPtr^),0);
                end
            else
              with PFreeRec(HeapPtr)^ do
                begin
                  Next := Block_Address[1];
                  Size := Ptr(Seg(HeapEnd^)-Seg(HeapPtr^),0);
                end;

            { HEAPPTR MUST BE IN THE LAST FREE BLOCK SO
              THAT TP6 DOESN'T TRY TO USE ANY MEMORY BETWEEN
              640K AND HEAPPTR }

            HeapPtr := Block_Address[Num_Blocks];
            HeapEnd := Ptr(Seg(Block_Address[Num_Blocks]^)+Seg(Block_Size[Num_Blocks]^),0);
          end;
      end;
  End;

{----------------------------------------------------------------------------}

BEGIN
  UMB_Heap_Debug := False;
  Num_Blocks := 0;
  SaveExitProc := ExitProc;
  ExitProc := @Release_UMB;
END.

{----------------------------------------------------------------------------}
