tags:

views:

616

answers:

3

Hi, I have a console application that I launch from a GUI applicaiton. The console application takes parameters for filenames to parse and process. Currently I am able to capture its output and display it in the GUI application but I would like to be able to send commands to it so as to control or even halt its execution.

How can I send a command or string or anything to the console application, preferably using the pipes that I opened in order to read its output?

const
  CReadBuffer = 2400;
var
  saSecurity: TSecurityAttributes;
  hRead: THandle;
  hWrite: THandle;
  suiStartup: TStartupInfo;
  piProcess: TProcessInformation;
  pBuffer: array[0..CReadBuffer] of AnsiChar;
  dRead: DWord;
  dRunning: DWord;
  dWritten: DWord;
  Command: String;
  BytesLeft: Integer;
  BytesAvail: Integer;
begin
  saSecurity.nLength := SizeOf(TSecurityAttributes);
  saSecurity.bInheritHandle := True;
  saSecurity.lpSecurityDescriptor := nil;

  if CreatePipe(hRead, hWrite, @saSecurity, 0) then
  begin
    FillChar(suiStartup, SizeOf(TStartupInfo), #0);
    suiStartup.cb := SizeOf(TStartupInfo);
    suiStartup.hStdInput := hRead;
    suiStartup.hStdOutput := hWrite;
    suiStartup.hStdError := hWrite;
    suiStartup.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
    suiStartup.wShowWindow := SW_HIDE;
    Command := 'messageparser.exe c:\messagefile.msg';
    UniqueString(Command);
    if CreateProcess(nil, PChar(Command), @saSecurity,
     @saSecurity, True, NORMAL_PRIORITY_CLASS, nil, nil, suiStartup, piProcess) then
    begin
      repeat
        dRunning  := WaitForSingleObject(piProcess.hProcess, 100);
        Application.ProcessMessages;
        repeat
          dRead := 0;

          if not PeekNamedPipe(hread, @pbuffer, CReadBuffer, @dRead, @BytesAvail, @BytesLeft) then
            RaiseLastOSError;
          if dRead <> 0 then
          begin
            ReadFile(hRead, pBuffer[0], CReadBuffer, dRead, nil);
            pBuffer[dRead] := #0;
            OemToCharA(pBuffer, pBuffer);
            // do something with the data
            // if a condition is present then do the following:
            // WriteFile(hWrite, some_command, size_of_buffer, DWritten, nil);  
          end;
        until (dRead < CReadBuffer);
      until (dRunning <> WAIT_TIMEOUT);
      CloseHandle(piProcess.hProcess);
      CloseHandle(piProcess.hThread);
    end;
    CloseHandle(hRead);
    CloseHandle(hWrite);
  end;

Then on the console side, there is a thread waiting for the input. Here is the execute method:

  while not Terminated do
  begin
    ReadLn(Command);
    // process command
    Sleep(10);
  end;

This is new to me so if there are tips on how do it right, I welcome them :). However whenever I send a Command, it comes over as whatever I read in the pBuffer from the ReadPipe and not what the command is.

Hope this helps.

--

Found a solution based on the tip by Nat.

Bi-directional communication between gui and console

+1  A: 

Along with output pipeline there's an input pipeline. Just write to that pipe using WriteFile().

Seva Alekseyev
Yeah I suspect that, but in the console, were is that picked up? I'm assuming that it's a ReadLn I need so I tried checking for that in a thread, however the input is the same as what is being outputted from the console app. It is not getting the commands from the GUI app, almost like it is reading itself. Maybe I'm doing something wrong.
yozey
Depending on how you write to the process, you might have to flush. If you use WriteFile() API, you don't have to though. The process's stdin handle is in the STARTUPINFO record, in the hStdInput field.If the console app is not reading its input, there's nothing you can do about it.
Seva Alekseyev
Currently it has a thread that executes a Readln command but the info is a mirror or what it outputs in its Writeln. So I was wondering why is that.
yozey
Um, a Captain Obvious comment: the input and the output are two different handles on both sides. You read to one, write to the other (there's also stderr, but that's another story). Writing to the console process' output pipe won't arrive to the console process as input.
Seva Alekseyev
See my response to fupsduck because I think you are not understanding my problem. I know the handles are different however the data that goes to the console is not the data I sent using WriteFile with the WritePipe handle as supplied by StartupInfo.
yozey
+1  A: 

Check this out to see that you need to create both pipes (by calling the WINAPI twice) as reiterated by Nat but what about the inheritable handles - not sure why this is needed?

http://support.microsoft.com/kb/190351.

I think what might also be confusing is that when you create a pipe you are creating a read handle and a write handle for that pipe. In the case of the console's stdin pipe you will only use the write handle. Then you create another pipe for the console's stdout (which will also have a read and write handle) but you will only use the read handle.

I believe I have that correct but it is late and I'm going to bed.

fupsduck
I do have the source code for the console app. In the GUI app I am able to read the console app's output using the following - ReadFile(ReadPipe, pBuffer[0], BufferSize, BytesRead, nil). This works as ReadPipe is supplied by the StartupInfo passed to CreateProcess. Now, I assume that move data the other way I use the WriteFile with WritePipe supplied by the same startupinfo. Now when I do this, the information I send comes to the console app as exactly what was in the ReadPipe and not what I want to send. Hope that clears up what I'm trying to say.
yozey
+5  A: 

You need two pipes, one for the process to send output to you (stdout), and one for you to send input to the process (stdin).

From your code, it looks like you are putting both ends of the same pipe into the TStartupInfo record. So you are effectively making the process talk to itself. :-)

So, you need to call CreatePipe() twice, to create two pipes, one for stdin, one for stdout (and stderr).

Then, put the reading handle of stdin in suiStartup.hStdInput and the writing handle of stdout in suiStartup.hStdOutput

To send data to the process, write to the write handle of the stdin pipe. To read the output of the process, read the read handle of the stdout pipe.

Edit: (again)

As for all the duplicating handles and inheritable and non-inheritable stuff described on this page (specifically in the code example), you need to make sure the handles you send to the process are inheritable (as you have done).

You should also make sure the handles of the pipes that the parent process use are not inheritable. But you don't have to do this... I've gotten away with not doing it before.

You can do this by either calling DuplicateHandle() on the handles, specifying they are not inheritable and closing the old handles, or calling SetHandleInformation() with 0 specified for the flags (as discussed here).

It's been a while since I have done this myself, but I'm pretty sure this is so that the reference count for the handles is associated with the calling process, rather than the child process. This prevents a handle being closed whilst you're still using it (the calling process might close 'stdin' for example). Make sure you close the handles though, otherwise you will end up leaking handles.

HTH.

N@

Nat
+1 for clarity and brevity but what do you make of the inheritable handles described here http://support.microsoft.com/kb/190351.
fupsduck
responded in answer. :)
Nat
Thanks for answer. Based on your hint about creating the pipe twice, I came upon an article that addresses my needs perfectly. I've included a link to it.
yozey