Passing data to Created Process via StdIn

(I thought this should, perhaps, be in the General Help category, but it seems I cannot create a new topic there)
I have this console app that is started from a GUI app, historically it is passed parameters in the command line and writes to std out which is read by the GUI app when the console app finishes.
Due to changes I need to write to the app as it is running, and I chose to do that by getting the app to read StdIn, after checking that something is available to be read.
Running from the command line, this updated app works just fine.
Running from the GUI it crashes with System Error 6 as soon as it tries to read stdin. The stdin handle is not nul.
This is the creation code, roughly.

  with SA do begin
    nLength := SizeOf(SA);
    bInheritHandle := True;
    lpSecurityDescriptor := nil;
  end;
  CreatePipe(StdInPipeRead[curtask], StdInPipeWrite[curtask], @SA, 0);
  CreatePipe(StdOutPipeRead[curtask], StdOutPipeWrite[curtask], @SA, 0);
  with SI[curtask] do
  begin
    FillChar(SI[curtask], SizeOf(SI[curtask]), 0);
    cb := SizeOf(SI[curtask]);
    dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
    wShowWindow := SW_HIDE;

    hStdInput  := StdInPipeRead[curtask];
    hStdOutput := StdOutPipeWrite[curtask];
    hStdError  := StdOutPipeWrite[curtask];
  end;
  Handle[curTask] := CreateProcess(nil, PChar(Cmd + ' '),
                             @SI[curTask], @SI[curTask], {nil, nil,} True, CREATE_NEW_CONSOLE or SYNCHRONIZE{0}, nil,
                             nil, SI[curTask], PI[curTask]);
  CloseHandle(StdOutPipeWrite[curTask]); // doesn't matter if commented out
  CloseHandle(StdInPipeRead[curTask]); // doesn't matter if commented out

Any ideas as to what is wrong?

Hi Mark

I fixed the permissions issue with general help and moved your post… I also edited the code snippet to enable syntax highlighting.

I have found redirecting StdIn to be rather difficult to acheive so far - I have code that sort of works but cannot really share it without sharing a tone of other code I’m not able to share.

One thing I do see missing in your code is you are not duplicating the handles… which is usually required, otherwise they will be closed by the process - so any use of them after that would cause invalid handle errors (system error 6).

This is my code to create the handles (called multiple times depending on which io is being redirected)

procedure TFBProcess.CreateRedirectPipes(var readHandle: THandle; var writeHandle: THandle; const dupRead: boolean; const bInherit: boolean = False);
var
  SecurityInfo: TSecurityAttributes;
  tmpHandle: THandle;
begin
  SecurityInfo.nLength := SizeOf(TSecurityAttributes);
  SecurityInfo.bInheritHandle := TRUE; // pipe handles can be inherited
  SecurityInfo.lpSecurityDescriptor := nil;
  if dupRead then
  begin
    CreatePipe(tmpHandle, writeHandle, @SecurityInfo, 0);
    if (tmpHandle = INVALID_HANDLE_VALUE) or (writeHandle = INVALID_HANDLE_VALUE) then
      raise EOSError.Create('Could not create pipe handles for redirect : ' + SysErrorMessage(GetLastError));
    if not DuplicateHandle(GetCurrentProcess, tmpHandle, GetCurrentProcess, @readHandle, 0, bInherit, DUPLICATE_SAME_ACCESS) then
      raise EOSError.Create('Could not duplicate pipe ' + SysErrorMessage(GetLastError));
  end
  else
  begin
    CreatePipe(readHandle, tmpHandle, @SecurityInfo, 0);
    if (readHandle = INVALID_HANDLE_VALUE) or (tmpHandle = INVALID_HANDLE_VALUE) then
      raise EOSError.Create('Could not create pipe handles for redirect : ' + SysErrorMessage(GetLastError));
    if not DuplicateHandle(GetCurrentProcess, tmpHandle, GetCurrentProcess, @writeHandle, 0, bInherit, DUPLICATE_SAME_ACCESS) then
      raise EOSError.Create('Could not duplicate pipe ' + SysErrorMessage(GetLastError));
  end;
  CloseHandle(tmpHandle);
end;

for stdin it’s called with CreateRedirectPipes(hInputRead, hInputWrite, False);
for stdout CreateRedirectPipes(hOutputRead, hOutputWrite, True);
for stderr CreateRedirectPipes(hErrRead, hErrWrite, True);

Working with pipes to control other applications is best done in separate threads… I have yet to find a good example in delphi that does this properly and not one that I have tried actually worked with stdin.

Anyway, hope that helps some.

Have a look at

It is used to redirect stdin and stdout for programs that you start yourself… but I believe it could be modified to to deal with using the handles on the calling program.

Well, I tried both possibilities and a few ‘mixes’ in between, but for this program error 6 is always occurring.

There’s a hint in a stack exchange post

This error 6 means “invalid handle” and means that the handle you have passed to GetConsoleMode is not a console handle. I.e. the STDIN has been redirected from somewhere.

To ensure you have the handle to the current console, regardless of whether STDIN is redirected from somewhere else, use CreateFile with the special name "CONIN$", which opens the console (if present).

And, yes, I am using GetConsoleMode, and SetConsoleMode and GetNumberOfConsoleInputEvents and PeekConsoleInput and ReadConsole, because I need to only read the console when there is actually something there. (Mind you even readln doesn’t work properly from within TurboPack DOSCommand, but I can’t use that here)

The same stack exchange entry suggests using CreateFile(‘CONIN$’, GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, NiL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) to avoid the error, but then you can’t use GetConsoleMode and the like as the behavour completely changes.

Maybe I can use ‘CONIN$’ and just treat it as a file (pipe?) and do away with the console routines competely.

Any ideas?