[英]How to use an interactive command line program from another .NET program
我需要为交互式命令行程序编写一个包装器。
这意味着我需要能够通过其标准输入向另一个程序发送命令并通过其标准输出接收响应。
问题是,当输入流仍然打开时,标准输出流似乎被阻塞了。 一旦我关闭输入流,我就会得到响应。 但后来我无法发送更多命令。
这是我目前正在使用的(主要来自这里):
void Main() {
Process process;
process = new Process();
process.StartInfo.FileName = "atprogram.exe";
process.StartInfo.Arguments = "interactive";
// Set UseShellExecute to false for redirection.
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
// Redirect the standard output of the command.
// This stream is read asynchronously using an event handler.
process.StartInfo.RedirectStandardOutput = true;
// Set our event handler to asynchronously read the output.
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
// Redirect standard input as well. This stream is used synchronously.
process.StartInfo.RedirectStandardInput = true;
process.Start();
// Start the asynchronous read of the output stream.
process.BeginOutputReadLine();
String inputText;
do
{
inputText = Console.ReadLine();
if (inputText == "q")
{
process.StandardInput.Close(); // After this line the output stream unblocks
Console.ReadLine();
return;
}
else if (!String.IsNullOrEmpty(inputText))
{
process.StandardInput.WriteLine(inputText);
}
}
}
我还尝试同步读取标准输出流,但结果相同。 对输出流的任何方法调用都会无限期地阻塞,直到输入流关闭 - 甚至是Peek()
和EndOfStream
。
有没有办法以全双工方式与其他进程通信?
我试图用我自己的一个小型测试套件重现您的问题。 我没有使用事件处理程序,而是以我能想到的最简单的方式做到这一点:同步。 这样就不会给问题增加额外的复杂性。
在这里,我用 rust 写了我的小“echoApp”,只是为了笑声,也有机会遇到永恒的线路终止战争问题( \\n
vs \\r
vs \\r\\n
)。 根据您编写命令行应用程序的方式,这确实可能是您的问题之一。
use std::io;
fn main() {
let mut counter = 0;
loop {
let mut input = String::new();
let _ = io::stdin().read_line(&mut input);
match &input.trim() as &str {
"quit" => break,
_ => {
println!("{}: {}", counter, input);
counter += 1;
}
}
}
}
而且 - 作为一个懒惰的人,不喜欢为这样一个小测试创建解决方案,我使用 F# 而不是 C# 作为控制端 - 我认为它很容易阅读:
open System.Diagnostics;
let echoPath = @"E:\R\rustic\echo\echoApp\target\debug\echoApp.exe"
let createControlledProcess path =
let p = new Process()
p.StartInfo.UseShellExecute <- false
p.StartInfo.RedirectStandardInput <- true
p.StartInfo.RedirectStandardOutput <- true
p.StartInfo.Arguments <- ""
p.StartInfo.FileName <- path
p.StartInfo.CreateNoWindow <- true
p
let startupControlledProcess (p : Process) =
if p.Start()
then
p.StandardInput.NewLine <- "\r\n"
else ()
let shutdownControlledProcess (p : Process) =
p.StandardInput.WriteLine("quit");
p.WaitForExit()
p.Close()
let interact (p : Process) (arg : string) : string =
p.StandardInput.WriteLine(arg);
let o = p.StandardOutput.ReadLine()
// we get funny empty lines every other time...
// probably some line termination problem ( unix \n vs \r\n etc -
// who can tell what rust std::io does...?)
if o = "" then p.StandardOutput.ReadLine()
else o
let p = createControlledProcess echoPath
startupControlledProcess p
let results =
[
interact p "Hello"
interact p "World"
interact p "Whatever"
interact p "floats"
interact p "your"
interact p "boat"
]
shutdownControlledProcess p
在 f# Interactive 中执行此操作(Visual Studio 中的 CTRL-A ALT-Enter)会产生:
val echoPath : string = "E:\\R\\rustic\\echo\\echoApp\\target\\debug\\echoApp.exe"
val createControlledProcess : path:string -> Process
val startupControlledProcess : p:Process -> unit
val shutdownControlledProcess : p:Process -> unit
val 交互:p:Process -> arg:string -> string
val p : Process = System.Diagnostics.Process
val 结果:字符串列表 =
["0: 你好"; “1:世界”; “2:随便”; “3:浮动”; “4:你的”; “5:船”]
验证它:单位 = ()
我无法重现任何阻塞或死锁等。因此,在您的情况下,我会尝试调查您的NewLine
属性是否需要进行一些调整(请参阅函数startupControlledProcess
。如果受控应用程序无法将输入识别为一行,则它可能不会响应, 仍在等待输入行的其余部分,您可能会得到您所拥有的效果。
process.BeginOutputReadLine();
不像预期的那样工作,因为它一直等到输出流关闭,这将在进程结束时发生,而在其输入流将关闭时进程将结束。 作为解决方法,只需使用process.StandardOutput.ReadLine()
和自己制作的异步的组合
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.