Link to home
Start Free TrialLog in
Avatar of Rcm
Rcm

asked on

Redirecting output when using exec command in Pascal

I'm using a graphical interface in Pascal and I don't want the childprocesses to return ANYTHING back to screen

(so both 1 and 2 (error) should be redirected to nul or to a file when I specify > in my command-string)

Please help me and help me quickly ... This needs to be operational Wednesday 20th June.

Function ShellCommand(Command : String) : Integer;
Var
  Redirect        : Boolean;
  OutputTo        : String;
  Param          : String;

Begin
  Redirect := (Pos('>', Command) <> 0);
  If Redirect Then
    OutputTo := ''
  Else
    OutputTo := ' 1>nul';
  Param := Copy(Command, Pos(' ', Command) + 1, Length(Command) - Pos(' ', Command));
  Delete(Command, Pos(' ', Command), Length(Command) - Pos(' ', Command) + 1);
  SwapVectors;
  Exec(Command, param + OutputTo);
  SwapVectors;
  ShellCommand := DosExitCode;
End;
Avatar of Rcm
Rcm

ASKER

The redirect works when I use the GetEnv('ComSpec') as command and the real command as parameter to it (with /c). This way I'm able to redirect it, BUT using command.com as command will reset dosexitcode and doserror ...
Avatar of dbrunton
Q: How do I execute an MS-DOS command from within a TP program?

 A: The best way to answer this question is to give an example.
  {$M 2048, 0, 0}   (* <-- Important *)
  program outside;
  uses dos;
  begin
    write ('Directory call from within TP by Timo Salmi');
    SwapVectors;
    Exec (GetEnv('comspec'), '/c dir *.*');  (* Execution *)
    SwapVectors;
    (* Testing for errors is recommended *)
    if DosError <> 0 then
      writeln ('Dos error number from ' + GetEnv('comspec'), DosError)
    else
      writeln ('Mission accomplished, exit code ', DosExitCode);
    (* For DosError and DosExitCode details see the TP manual *)
  end.
   Alternatively, take a look at execdemo.pas from demos.arc which
should be on the disk accompanying Turbo Pascal.
   What the above Exec does is that it executes the command
processor (in fact, a new copy of the command processor.) The /c
specifies that the command interpreter is to perform the command,
and then close (not halt). Note that the command (in the example dir
*.*) can be an MS-DOS command, a program, or a batch file.
  Note that the DosError and DosExitCode are returned by the first
program in the Exec call, i.e. the command interpreter in the above
example, not by the item in the latter part.
  Somewhat surprisingly some users have had difficulties with
redirecting shelled output. It is straight-forward. In the above
code one would use, for example
     Exec (GetEnv('comspec'), '/c dir *.* > tmp');
  Calling a batch file from within a TP program via the command
interpreter is no different from calling an executable program via
it. Thus, you could use any of the examples below
     Exec (GetEnv('comspec'), '/c ' + 'mybatch.bat');
     Exec (GetEnv('comspec'), '/c ' + 'myprog.exe par1 par2 par3');
     Exec (GetEnv('comspec'), '/c ' + 'myprog2.com par');
     Exec (GetEnv('comspec'), '/c ' + 'dir *.*');
     Exec (GetEnv('comspec'), '/c type myfile.txt | more');
The extensions .bat, .exe and .com can be, and usually are omitted.
They are given here only for the clarity of the examples.
   What the above examples do is that they issue the commands via
the command interpreter. An executable program (but not a batch,
since it must be executed via the command interpreter) can also be
called directly:
     Exec ('c:\progdir\myprog.exe', 'par1 par2 par3');
For an example see item #75. A tip from Pedt Scragg: "When calling
the program directly, as in this example, then the extension .exe or
.com needs to be present."

 A2: I have also seen it asked how one can swap the Turbo Pascal
program to the disk when shelling. It is unnecessary to program that
separately because there is an excellent program to do that for you.
It is ftp://garbo.uwasa.fi/pc/sysutil/shrom24b.zip. Also of interest
to advanced programmers even if in C
Avatar of Rcm

ASKER

To dbrunton:

I don't know if you noticed I removed the GetEnv('ComSpec') and call the program directly ... I did this ON PURPOSE ... because, like you explain ... the dosexitcode and doserror will only be the ones of the program that were executed (and command.com will handle all exceptions itself and by this will return doserror 0 and dosexitcode 0) I really need those codes of the programs that are executed and not the codes of command.com. (always 0). Now ... the redirection works fine when using command.com, but they don't when I call these programs directly. My question was how I could prevent these programs to return anything to screen (because I have a graphical interface and when these programs have output, my graphical interface is all messed up.)

ps. I use {$M 12288, 0, 40000}
I can't lower these values by much anymore ... I'm using an internal copy command (blockread and write), PCX-files, a huge list of variables, ... and I also do a getImage and putImage (when I use a "pop-up"-window) ...

I just don't want ANY output from the exec-command to mess-up my graphical interface. And I need the errorlevels and doserrors to check if the command was successfull (wich I cannot check when using exec(getenv('comspec'), ' /c ' + command);
Avatar of Rcm

ASKER

Oh, btw ... the output of some programs need to be in a file ... wich I then can read so I can print it with the OutTextXY command or use it as a variable (like I have a program to read the serial number of a dell and compaq-computer ... the number normally will be reported to screen, but I redirect it to a file and then read it and use the number as parameter to some other commands)
OK

Here are some possible solutions.  There are about 9 small little code samples from the SWAG archive here and in the next postings to do with redirection and exec.

Unit dualout;

{ This Unit is designed to demonstrate directing all screen output to a File }
{ in addition to the normal display.  This means that any Write or Writeln   }
{ will display normally on the screen and also be Recorded in a Text File.   }
{ The File name For the output can be supplied by a command line parameter   }
{ in the Format -  dual=c:\test\output.dat or you can provide an environment }
{ Variable named dual that supplies the File name or it will default to the  }
{ current directory and output.dat.                                          }

Interface

Uses
  globals,  { contains the Function exist, which tests For the existence of  }
            { a File.  It also defines the Type str80 as String[80]          }
  Dos,
  tpString; { from TPro. Needed For StUpCase Function in Procedure initialise}

Const
  DualOn   : Boolean = False;
  DualOK   : Boolean = False;
  fname    : str80   = 'output.dat';  { The default File name For the output }
 
Type
  DriverFunc = Function(Var f: TextRec): Integer;

Var
  OldExitProc    : Pointer;                  { For saving old Exit Procedure }
  OldInOutOutput,                            { The old output InOut Function }
  OldFlushOutput : DriverFunc;               { The old output Flush Function }
  dualf          : Text;

Procedure  dual(status: Boolean);

{===========================================================================}
Implementation

Var
  cmdline : String;
 
Procedure DualWrite(Var f: TextRec);
  { Writes the output from stdout to a File }
  Var
    x : Word;
  begin
    For x := 0 to pred(f.BufPos) do
      Write(dualf, f.BufPtr^[x]);
  end;  { DualWrite }

{$F+}
Function InOutOutput(Var f: TextRec): Integer;
  begin
    DualWrite(f);                                        { Write to the File }
    InOutOutput := OldInOutOutput(f);                { Call the old Function }
  end; { InOutOutput }

Function FlushOutput(Var f: TextRec): Integer;
  begin
    DualWrite(f);                                        { Write to the File }
    FlushOutput := OldFlushOutput(f);                { Call the old Function }
  end; { FlushOutput }

Procedure DualExitProc;
  begin
    close(dualf);
    ExitProc := OldExitProc;                { Restore the old Exit Procedure }
    With TextRec(output) do begin
      InOutFunc := @OldInOutOutput;          { Restore the old output Record }
      FlushFunc := @OldFlushOutput;           { Restore the old flush Record }
    end; { With }
  end; { DualExitProc }

{$F-,I-}
Procedure dual(status: Boolean);
  Var
    ErrorCode : Integer;
  begin
    if status then begin
      assign(dualf,fname);
      if Exist(fname) then { open For writing }
        append(dualf)
      else { start new File }
        reWrite(dualf);
      ErrorCode := Ioresult;  
      if ErrorCode <> 0 then
        halt(ErrorCode);
      With TextRec(output) do begin
        { This is where the old output Functions are rerouted }
        OldInOutOutput := DriverFunc(InOutFunc);
        OldFlushOutput := DriverFunc(FlushFunc);
        InOutFunc := @InOutOutput;
        FlushFunc := @FlushOutput;
      end; { With }
      OldExitProc := ExitProc;            { Save the current Exit Procedure }
      ExitProc    := @DualExitProc;            { Install new Exit Procedure }
      DualOn      := True;
    end { if status }  
    else { switch dual output off } begin  
      if DualOn then begin
        close(dualf);  if Ioresult = 0 then;                   { dummy call }
        ExitProc := OldExitProc;           { Restore the old Exit Procedure }
        OldExitProc := nil;
        With TextRec(output) do begin
          InOutFunc := @OldInOutOutput;     { Restore the old output Record }
          FlushFunc := @OldFlushOutput;      { Restore the old flush Record }
        end; { With }
      end; { if DualOn }
    end; { else }
  end; { dual }
{$I+}  


Procedure Initialise;
  { Determines if a File name For the output has been provided. }
  begin
    if GetEnv('DUAL') <> '' then
      fname := GetEnv('DUAL')
    else begin
      if ParamCount <> 0 then begin
        cmdline := String(ptr(PrefixSeg,$80)^);
        cmdline := StUpCase(cmdline);
        if pos('DUAL=',cmdline) <> 0 then begin
          fname := copy(cmdline,pos('DUAL=',cmdline)+5,80);
          if pos(' ',fname) <> 0 then
            fname := copy(fname,1,pos(' ',fname)-1);
        end; { if pos('Dual... }
      end;  { if ParamCount... }
    end; { else }
  end; { Initialise }
 
begin
  Initialise;
end.  

*********

{
MARK LEWIS

>> Still need a bit of help here.  I can't redirect output from a
>> Program when executing it from a Pascal Program!  Is there any
>> this from Pascal? Any help would be greatly appreciated.
> if I understand you, you are using the Exec Procedure to run a
> Program.  if that is the Case you won't be ablr to redirect since
> this is a Function of Dos and not the Program you exec.  You will
> need to run the Program through a child process in order to
> perform the redirect, something like:
> Exec(GetEnv('COMSPEC'),'/C MyProg.exe>redirect');

one could also utilize duplicate File handles -=B-)

}
Unit Execute;

Interface

Procedure Exec(Path, CmdLine : String);

Implementation

Uses
  Dos;

Function ExtractFileName(Var Line : String; Index : Integer) : String;
Var
  Temp : String;
begin
  Delete(Line, Index, 1);
  While (Index <= Length(Line)) and (Line[Index] = ' ') Do
    Delete(Line, Index, 1);
  Temp := '';
  While (Index <= Length(Line)) and (Line[Index] <> ' ') Do
  begin
    Temp := Temp + Line[Index];
    Delete(Line, Index, 1);
  end;
  ExtractFileName := Temp;
end;

Procedure CloseHandle(Handle : Word);
Var
  Regs : Registers;
begin
  With Regs Do
  begin
    AH := $3E;
    BX := Handle;
    MsDos(Regs);
  end;
end;

Procedure Duplicate(SourceHandle : Word;Var TargetHandle : Word);
Var
  Regs : Registers;
begin
  With Regs Do
  begin
    AH := $45;
    BX := SourceHandle;
    MsDos(Regs);
    TargetHandle := AX;
  end;
end;

Procedure ForceDuplicate(SourceHandle : Word;Var TargetHandle : Word);
Var
  Regs : Registers;
begin
  With Regs Do
  begin
    AH := $46;
    BX := SourceHandle;
    CX := TargetHandle;
    MsDos(Regs);
    TargetHandle := AX;
  end;
end;

Procedure Exec(Path,CmdLine : String);
Var
  StdIn,
  Stdout    : Word;
  Index     : Integer;
  FName     : String[80];
  InFile,
  OutFile   : Text;
  InHandle,
  OutHandle : Word;
         { ===============>>>> }   { change below For STDERR }
begin
  StdIn  := 0;
  StdOut := 1;                    { change to 2 For StdErr       }
  Duplicate(StdIn, InHandle);      { duplicate standard input     }
  Duplicate(StdOut, OutHandle);    { duplicate standard output    }
  Index := Pos('>', CmdLine);
  if Index > 0 Then               { check For output redirection }
  begin
    FName := ExtractFileName(CmdLine, Index);  { get output File name  }
    Assign(OutFile, FName);                    { open a Text File      }
    ReWrite(OutFile);                         { .. For output         }
    ForceDuplicate(TextRec(OutFile).Handle, StdOut);{ make output same }
  end;
  Index := Pos('<', CmdLine);
  if Index > 0 Then               { check For input redirection }
  begin
    FName := ExtractFileName(CmdLine, Index);  { get input File name  }
    Assign(InFile, FName);                     { open a Text File     }
    Reset(InFile);                            { For input            }
    ForceDuplicate(TextRec(InFile).Handle, StdIn);  { make input same }
  end;
  Dos.Exec(Path, CmdLine);           { run EXEC }
  ForceDuplicate(InHandle, StdIn);   { put standard input back to keyboard }
  ForceDuplicate(OutHandle, StdOut); { put standard output back to screen  }
  CloseHandle(InHandle);            { close the redirected input File     }
  CloseHandle(OutHandle);           { close the redirected output File    }
end;

end.

{===============================================================}
{
Use it exactly as you would the normal EXEC Procedure:

  Exec('MAsm.EXE','mystuff.Asm');

To activate redirection simply add the redirection symbols, etc:

  Exec('MAsm.EXE','mystuff.Asm >err.lst');


One note of caution.  This routine temporarily Uses extra handles. It's
either two or four more.  The Various books I have are not clear as to
whether duplicated handles 'count' or not. My guess is yes.  if you don't
plan on redirecting STDIN then remove all the code For duplicating it to
cut your handle overhead in half.
}

ASKER CERTIFIED SOLUTION
Avatar of dbrunton
dbrunton
Flag of New Zealand image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Apologies here.  I've gone and done a very rough grab from the SWAG archive and grabbed what looked useful.  They discuss some of the problems you may have and various solutions.

I didn't have time to sort it out for usefulness.  I have broken it into sections by the use of

**********

to show where the breaks are.

You will find the solutions look like they use handles for redirections.  Note they don't cover the use of graphics windows.  You may have to shoot back to a text based window for some of these solutions to work and then recall your graphic window afterwards.
Avatar of Rcm

ASKER

It is working, but now I have troubles with my memory again. It seems like when I try to include one functionality, I loose another.
I'm unable to load the network at this time.
And I can't lower the heap anymore neither the stack.

Aaaargh ... I'm really growing a deep sense of hatered towards dos programming.
Avatar of Rcm

ASKER

I had to do some digging into the code myself, but the help was verry usefull.