Contributor: CARL YORK                

{ A bit wordy - but easy to include in an application - three "hooks" in }
{ the form of the first three internal procedures to customize the code. }
{ NOTE! MaxHeap must be limited to allow the EXEC procedure to function. }
{ By Carl York with code by Neil J. Rubenking and Richard S. Sandowsky.  }

UNIT DOSShell;

INTERFACE
procedure ShellToDOS;

IMPLEMENTATION
USES CRT, DOS;

procedure ShellToDOS;
const
  SmallestAllowableRam = 5;                   { Set   }
  Normal               = 7;                   { to    }
  Reverse              = 112;                 { your  }
  ApplicationName      = 'MY OWN PROGRAM';    { specs }
var
  ProgramName,
  CmdLineParam,
  NewDirect,
  HoldDirect     : PathStr;
  HoldAttr       : byte;
  HoldMin,
  HoldMax        : word;
  SlashSpot,
  BlankSpot      : byte;

{+++++++++++++++++++++++++++++++}
procedure PrintMessage;
begin
  { Clever message to make your end user feel foolish }
end;
{-------------------------------}

{++++++++++++++++++++++}
procedure SwapScreenOut;
begin
  { Whatever routine you want to use to    }
  { save the contents on the active screen }
end;
{---------}

{++++++++++++++++++++++}
procedure SwapScreenIn;
begin
  { Whatever routine you want to use to }
  { restore the contents on the screen  }
end;
{---------}

{+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
function GetProgramToRun : PathStr;
{ Courtesy of Neil Rubenking, this code duplicates the way DOS normally }
{ searches the path for a file name typed in at the DOS level using the }
{ TP5 routines FSearch and FExpand (code published PC Magazine 1/17/89) }
var
  Name : PathStr;
begin
  Name := FSearch(ProgramName + '.COM','');          { Search    }
  If Name = '' then                                  { the       }
    Name := FSearch(ProgramName + '.EXE','');        { active    }
  If Name = '' then                                  { drive/    }
    Name := FSearch(ProgramName + '.BAT','');        { directory }
  If Name = '' then
    Name := FSearch(ProgramName + '.COM',GetEnv('PATH'));
  If Name = '' then                                          { Search }
    Name := FSearch(ProgramName + '.EXE',GetEnv('PATH'));    { the    }
  If Name = '' then                                          { path   }
    Name := FSearch(ProgramName + '.BAT',GetEnv('PATH'));
  If Name <> '' then
    Name := FExpand(Name);
  GetProgramToRun := Name;
end;
{------------------------------------------------------------------------}

{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
function RAMFreeInK : Word;
{ A tidy little chunk of Inline code from Rich Sandowsky }
Inline(
  $B8/$00/$48/           {  mov   AX,$4800  ; set for DOS function 48h}
  $BB/$FF/$FF/           {  mov   BX,$FFFF  ; try to allocate more RAM}
                         {                  ; than is possible}
  $CD/$21/               {  int   $21       ; execute the DOS call}
  $B1/$06/               {  mov   CL,6      ;}
  $D3/$EB/               {  shr   BX,CL     ; convert to 1K blocks}
  $89/$D8);              {  mov   AX,BX     ; return number of 1K blocks}
                         {                  ; RAM free as function result}
{------------------------------------------------------------------------}

{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
procedure WritePrompt;
{ Create a DOS prompt for the user }
begin
  TextAttr := Normal;
  Write('Temporarily in DOS (',RAMFreeInK,'K available) ... Type ');
  TextAttr := Reverse;
  Write('EXIT');
  TextAttr := Normal;
  WriteLn(' to return to ',ApplicationName);
  Write(NewDirect,'>');
end;
{------------------------------------------------------------------------}

{++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
procedure RunTheShell;
{ The actual use of the EXEC procedure }
var
  Index : integer;
begin
  GetDir(0,NewDirect);
  WritePrompt;
  CmdLineParam := '';
  ReadLn(ProgramName);
  For Index := 1 to length(ProgramName) do
    ProgramName[index] := Upcase(ProgramName[Index]);
  While ProgramName[length(ProgramName)] = #32 do
    Dec(ProgramName[0]);
  While (length(ProgramName) > 0) and (ProgramName[1] = #32) do
    Delete(ProgramName,1,1);
  If (ProgramName <> 'EXIT') then
    begin
      EXEC(GetEnv('COMSPEC'),'/C '+ ProgramName + CmdLineParam);
      { Brute force to see if we need to pursue any further }
      If Lo(DOSExitCode) <> 0 then
        begin
          BlankSpot := pos(' ',ProgramName);
          SlashSpot := pos('/',ProgramName);
          If SlashSpot > 0 then
            If (SlashSpot < BlankSpot) or (BlankSpot = 0) then
              BlankSpot := SlashSpot;
          If BlankSpot > 0 then
            begin
              CmdLineParam := copy(ProgramName,BlankSpot,Length(ProgramName));
              ProgramName[0] := Chr(pred(BlankSpot));
            end;
          ProgramName := GetProgramToRun;
          If ProgramName <> '' then
            If pos('.BAT',ProgramName) > 0 then
              EXEC(GetEnv('COMSPEC'),'/C '+ ProgramName + CmdLineParam)
            else EXEC(ProgramName,CmdLineParam);
        end;
    end;
  WriteLn;
end;
{------------------------------------------------------------------------}

{=================================}
begin
  If RamFreeInK <= SmallestAllowableRam then
    begin
      PrintMessage;
      EXIT;
    end;
  HoldAttr := TextAttr;           { Grab the current video attribute }
  GetDir(0,HoldDirect);           { Grab the current drive/path }
  HoldMin := WindMin;
  HoldMax := WindMax;             { And the current window }
  TextAttr := Normal;
  SwapScreenOut;
  Window(1,1,80,25);
  ClrScr;
  SwapVectors;
  Repeat
    RunTheShell;
  Until ProgramName = 'EXIT';
  SwapVectors;                      { Restore all the original set up }
  ChDir(HoldDirect);
  TextAttr := HoldAttr;
  Window(Lo(HoldMin),Hi(HoldMin),Lo(HoldMax),Hi(HoldMax));
  ClrScr;
  SwapScreenIn;
end;

END.