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

9

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

如何向控制台应用程序发送命令、字符串或任何东西,最好使用我打开的管道来读取其输出?

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;

然后在控制台端,有一个线程等待输入。这是执行方法:

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

这对我来说是新的,如果有正确操作的提示,我欢迎它们:)。但是每当我发送一个命令时,它都以我从ReadPipe中读取的pBuffer为准,而不是命令本身。

希望这可以帮到您。

--

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

GUI和控制台之间的双向通信


那么你有这两个应用程序的源代码吗?它们都是用Delphi编写的吗? - fupsduck
我添加了源代码。 - yozey
发布源代码加1分 - 这对于帮助他人有很大的帮助。 - fupsduck
谢谢。一开始就应该这样做。 - yozey
3个回答

9
你需要两个管道,一个用于进程将输出发送给你(stdout),另一个用于你向进程发送输入(stdin)。

从你的代码来看,你把同一个管道的两端都放入了TStartupInfo记录中。因此,你实际上是让进程自言自语。:-)

所以,你需要调用CreatePipe()两次,创建两个管道,一个用于stdin,一个用于stdout(和stderr)。

然后,将stdin的读取句柄放入suiStartup.hStdInput中,将stdout的写入句柄放入suiStartup.hStdOutput中。

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

编辑:(再次)

至于此页面(特别是代码示例中)描述的所有复制句柄、可继承和不可继承的内容,您需要确保发送到进程的句柄是可继承的(就像您所做的那样)。

应该还要确保父进程使用的管道句柄是不可继承的。但您不必须这样做...我以前曾经没有这样做过。

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

我已经有一段时间没有自己做过这个了,但我很确定这是为了将句柄的引用计数与调用进程关联起来,而不是与子进程关联起来。这可以防止在你仍在使用它时关闭句柄(例如,调用进程可能会关闭“stdin”)。请确保关闭句柄,否则你将会泄漏句柄。

希望对你有所帮助。

N@


你对这里描述的可继承句柄有什么看法?详见http://support.microsoft.com/kb/190351。 - fupsduck
感谢您的回答。根据您有关两次创建管道的提示,我找到了一篇完美解决我的需求的文章。我已经包含了一个链接。 - yozey

1
除了输出管道之外,还有一个输入管道。只需使用WriteFile()向该管道写入即可。

是的,我也怀疑是这样,但在控制台中,它从哪里获取?我假设需要一个ReadLn,因此我尝试在线程中检查它,但输入与从控制台应用程序输出的内容相同。它没有从GUI应用程序获取命令,就好像它正在读取自己。也许我做错了什么。 - yozey
根据你写入进程的方式,你可能需要刷新。如果你使用WriteFile() API,那么你就不需要刷新了。进程的stdin句柄在STARTUPINFO记录中的hStdInput字段中。如果控制台应用程序没有读取其输入,则无法解决此问题。 - Seva Alekseyev
目前它有一个线程执行Readln命令,但信息是其Writeln输出的镜像。所以我想知道为什么会这样。 - yozey
嗯,一个显而易见的评论:输入和输出在两侧是两个不同的句柄。您可以读取其中一个,写入另一个(还有stderr,但那是另一回事)。向控制台进程的输出管道写入内容不会作为输入到达控制台进程。 - Seva Alekseyev
请看我对fupsduck的回复,因为我认为你没有理解我的问题。我知道句柄是不同的,但是发送到控制台的数据并不是我使用StartupInfo提供的WritePipe句柄使用WriteFile发送的数据。 - yozey

1

看这个,你需要创建两个管道(通过调用WINAPI两次),如Nat所重申的那样,但关于可继承句柄-不确定为什么需要?

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

我认为可能令人困惑的是,当您创建管道时,您正在为该管道创建读取句柄和写入句柄。 对于控制台的stdin管道,您将仅使用写入句柄。 然后,您为控制台的stdout创建另一个管道(该管道也将具有读取和写入句柄),但您将仅使用读取句柄。

我相信我是正确的,但现在很晚了,我要睡觉了。


我有控制台应用程序的源代码。在GUI应用程序中,我可以使用以下方法读取控制台应用程序的输出 - ReadFile(ReadPipe, pBuffer[0], BufferSize, BytesRead, nil)。这是因为ReadPipe由传递给CreateProcess的StartupInfo提供。现在,我假设要将数据移动到另一个方向,我需要使用WriteFile和由同一startupinfo提供的WritePipe。但是,当我这样做时,我发送的信息正好像在ReadPipe中所看到的,而不是我想要发送的内容。希望这能澄清我的意思。 - yozey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接