如何从 GUI 应用程序向控制台应用程序发送命令

如何从 GUI 应用程序向控制台应用程序发送命令

问题描述:

我有一个从 GUI 应用程序启动的控制台应用程序.控制台应用程序采用文件名参数进行解析和处理.目前我能够捕获它的输出并将其显示在 GUI 应用程序中,但我希望能够向它发送命令以控制甚至停止它的执行.

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;

这对我来说是新的,所以如果有关于如何正确做的提示,我欢迎他们:).但是,每当我发送命令时,它都会作为我从 ReadPipe 在 pBuffer 中读取的任何内容出现,而不是命令是什么.

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.

希望这会有所帮助.

--

根据 Nat 的提示找到了解决方案.

Found a solution based on the tip by Nat.

gui 和控制台之间的双向通信>

您需要两个管道,一个用于进程向您发送输出(stdout),另一个用于您将输入发送到进程 (stdin).

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).

从您的代码来看,您似乎将 same 管道的两端放入 TStartupInfo 记录中.因此,您正在有效地使流程自言自语.:-)

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. :-)

因此,您需要调用 CreatePipe() 两次,以创建两个管道,一个用于 stdin,一个用于 stdout(和 stderr).

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

然后,将stdin的读句柄放在suiStartup.hStdInput中,将stdout的写句柄放在suiStartup.hStdOutput代码>

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

要向进程发送数据,请写入stdin 管道的写句柄.要读取进程的输出,请读取 stdout 管道的读取句柄.

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.

(再次)

至于在此页面上描述的所有重复句柄以及可继承和不可继承的东西(特别是在代码示例中),您需要确保您发送给进程的句柄是可继承的(正如您所做的那样).

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.

您可以通过在句柄上调用 DuplicateHandle(),指定它们不可继承并关闭旧句柄,或调用 SetHandleInformation() 并指定 0 来实现此目的对于标志(如此处所讨论).

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).

我自己已经有一段时间没有这样做了,但我很确定这是因为句柄的引用计数与调用进程相关联,而不是与子进程相关联.这可以防止句柄在您仍在使用时被关闭(例如,调用过程可能会关闭stdin").但是请确保关闭手柄,否则最终会导致手柄泄漏.

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@