6

How can I get Delphi to pass a string to the input pipe to a CMD process. I am able to get an error pipe and output pipe functioning properly, unfortunately not the input pipe. The code I am using is taken from an online tutorial for piping. There were several errors in the original code causing problems when it was compiled. They have been fixed but I am left with problems when trying to pass input still.

Here is the code in the Form.Create event. I also have included the WritePipe and ReadPipe methods. WritePipe does not work, ReadPipe does work. Both WriteFile and ReadFile in the Pipe methods return a successful message, only the ReadPipe actually works however.

var
    DosApp: String;
    DosSize: Integer;
    Security : TSecurityAttributes;
    start : TStartUpInfo;
    byteswritten: DWord;
    WriteString : ansistring;
  begin
    CommandText.Clear;
    // get COMSPEC variable, this is the path of the command-interpreter
    SetLength(Dosapp, 255);
    DosSize := GetEnvironmentVariable('COMSPEC', @DosApp[1], 255);
    SetLength(Dosapp, DosSize);

  // create pipes
    With Security do
      begin
        nlength := SizeOf(TSecurityAttributes) ;
        binherithandle := true;
        lpsecuritydescriptor := nil;
      end;

    CreatePipe(InputPipeRead, InputPipeWrite, @Security, 0);
    CreatePipe(OutputPipeRead, OutputPipeWrite, @Security, 0);
    CreatePipe(ErrorPipeRead, ErrorPipeWrite, @Security, 0);

    // start command-interpreter
    FillChar(Start,Sizeof(Start),#0) ;

    //start.hStdInput := InputPipeRead;
    start.hStdOutput := OutputPipeWrite;
    start.hStdError :=  ErrorPipeWrite;

    start.dwFlags := STARTF_USESTDHANDLES + STARTF_USESHOWWINDOW;
    start.wShowWindow := SW_Show;//SW_HIDE;
    start.cb := SizeOf(start) ;

    if CreateProcess('', PChar(DosApp), @Security, @Security, true,
               CREATE_NEW_CONSOLE or SYNCHRONIZE, // CREATE_NO_WINDOW,
               nil, nil, start, ProcessInfo) then
      begin
        MyThread := MainUnit.monitor.Create;  // start monitor thread
        MyThread.Priority := tpHigher;
      end;

    Button1.Enabled := true;
    cmdcount := 1;

end;

Write Pipe:

procedure WritePipeOut(OutputPipe: THandle; InString: PWideChar);
// writes Instring to the pipe handle described by OutputPipe
  var
    count : integer;
    byteswritten: DWord;
    outputstring : PAnsiChar;
    TextBuffer: array[1..32767] of AnsiChar;// char;
    TextString: String;
  begin

// most console programs require CR/LF after their input.

    InString := PWideChar(InString + #13#10);

    WriteFile(InputPipeWrite, InString[1], Length(InString), byteswritten, nil);

  end;

Read Pipe:

function ReadPipeInput(InputPipe: THandle; var BytesRem: Integer): String;
  {
    reads console output from InputPipe.  Returns the input in function
    result.  Returns bytes of remaining information to BytesRem
  }
  var
    TextBuffer: array[1..32767] of AnsiChar;// char;
    TextString: String;
    BytesRead: Cardinal;
    PipeSize: Integer;
  begin
    Result := '';
    PipeSize := length(TextBuffer);
    // check if there is something to read in pipe
    PeekNamedPipe(InputPipe, nil, PipeSize, @BytesRead, @PipeSize, @BytesRem);
    if bytesread > 0 then
      begin
        ReadFile(InputPipe, TextBuffer, pipesize, bytesread, nil);
        // a requirement for Windows OS system components
        OemToChar(@TextBuffer, @TextBuffer);
        TextString := String(TextBuffer);
        SetLength(TextString, BytesRead);
        Result := TextString;
      end;
  end;

Further note; this is for use with the Java Debugger, which requires input in stages and so I do not believe there is any alternative method other than manipulating input directly to the JDB.

Any help is much appreciated!

James_Hill
  • 167
  • 1
  • 4
  • 13
  • Jedi Code Library has a component, wrapping around CreateProcess API and converting input and output to TStream. And JCL installer is an example of using it. OTOH don't forget that in Windows graphical and text (including command line) programs using diferent language codepages. – Arioch 'The Apr 26 '13 at 10:12

1 Answers1

7

1) You should pass InputPipeRead as hStdInput into CreateProcess: uncomment your line start.hStdInput := InputPipeRead;

2) The WritePipeOut function has two errors: it writes a Unicode (UTF-16LE) string into a pipe, and it skips the first character (since it writes a memory area beginning at InString[1]). Instead of WriteFile(InputPipeWrite, InString[1], Length(InString),... you should write something like:

var AnsiBuf: AnsiString;
...
  AnsiBuf := String(InString) + #13#10;
  Write(InputPipeWrite, AnsiBuf[1], Length(AnsiBuf), byteswritten, nil);
nullptr
  • 11,008
  • 1
  • 23
  • 18
  • Thanks allot! Its working great now! I've not been able to find much documentation on Pipes so massive thanks for the help! – James_Hill Apr 26 '13 at 00:54