Как отправить команду на консольное приложение из приложения с графическим интерфейсом - PullRequest
9 голосов
/ 06 января 2010

У меня есть консольное приложение, которое я запускаю из приложения с графическим интерфейсом. Консольное приложение принимает параметры имен файлов для анализа и обработки. В настоящее время я могу захватить его вывод и отобразить его в приложении с графическим интерфейсом, но я хотел бы иметь возможность отправлять ему команды, чтобы контролировать или даже останавливать его выполнение.

Как я могу отправить команду или строку или что-нибудь в консольное приложение, предпочтительно используя каналы, которые я открыл, чтобы прочитать его вывод?

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;

Затем на стороне консоли есть поток, ожидающий ввода. Вот метод execute:

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

Это ново для меня, поэтому, если есть советы о том, как это сделать правильно, я приветствую их :). Однако всякий раз, когда я посылаю Команду, она появляется как то, что я читаю в pBuffer из ReadPipe, а не какова команда.

Надеюсь, это поможет.

-

Нашел решение на основе подсказки Нат.

Двунаправленная связь между графическим интерфейсом и консолью

Ответы [ 3 ]

9 голосов
/ 07 января 2010

Вам нужны два канала, один для процесса, чтобы отправить вывод вам (stdout), а другой для отправки ввода в процесс (stdin).

Из вашего кода похоже, что вы помещаете оба конца одинакового канала в запись TStartupInfo. Таким образом, вы эффективно заставляете процесс говорить с самим собой. : -)

Итак, вам нужно дважды вызвать CreatePipe(), чтобы создать два канала, один для stdin, один для stdoutstderr).

Затем поместите ручку чтения stdin в suiStartup.hStdInput и ручку записи stdout в suiStartup.hStdOutput

Чтобы отправить данные процессу, запишите в дескриптор записи канала stdin. Чтобы прочитать выходные данные процесса, прочитайте дескриптор чтения канала stdout.

Редактировать: (снова)

Что касается всех дублирующих дескрипторов и наследуемых и ненаследуемых вещей, описанных на этой странице (в частности, в примере кода), вам нужно , чтобы убедиться, что дескрипторы, которые вы отправляете процесс наследуется (как вы сделали).

Вы должны также убедиться, что дескрипторы каналов, которые использует родительский процесс, не наследуются . Но у вас нет , чтобы сделать это ... Я уже успел не делать этого раньше.

Вы можете сделать это, вызвав DuplicateHandle() на дескрипторах, указав, что они не наследуются и закрыв старые дескрипторы, или вызвав SetHandleInformation() с 0, указанным для флагов (как обсуждено здесь ) ,

Прошло много времени с тех пор, как я сам это сделал, но я уверен, что это так, что счетчик ссылок для дескрипторов связан с вызывающим процессом, а не с дочерним процессом. Это предотвращает закрытие дескриптора, пока вы все еще используете его (вызывающий процесс может закрыть 'stdin', например). Убедитесь, что вы закрыли ручки, в противном случае у вас будут протечки ручек.

НТН.

N @

1 голос
/ 07 января 2010

Проверьте это, чтобы увидеть, что вам нужно создать оба канала (дважды вызвав WINAPI), как это повторил Nat, но как насчет наследуемых дескрипторов - не знаете, зачем это нужно?

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

Я думаю, что также может сбивать с толку то, что когда вы создаете канал, вы создаете дескриптор чтения и дескриптор записи для этого канала. В случае канала stdin консоли вы будете использовать только дескриптор записи. Затем вы создаете другой канал для стандартного вывода консоли (который также будет иметь дескриптор чтения и записи), но вы будете использовать только дескриптор чтения.

Полагаю, я правильно понял, но уже поздно, и я иду спать.

1 голос
/ 06 января 2010

Наряду с выходным конвейером есть входной конвейер. Просто напишите в этот канал, используя WriteFile ().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...