简体   繁体   English

如何从另一个 .NET 程序使用交互式命令行程序

[英]How to use an interactive command line program from another .NET program

I need to write a wrapper for an interactive command line program.我需要为交互式命令行程序编写一个包装器。

That means I need to be able to send commands to the other program via its standard input und receive the response via its standard output.这意味着我需要能够通过其标准输入向另一个程序发送命令并通过其标准输出接收响应。

The problem is, that the standard output stream seems to be blocked while the input stream is still open.问题是,当输入流仍然打开时,标准输出流似乎被阻塞了。 As soon as I close the input stream I get the response.一旦我关闭输入流,我就会得到响应。 But then I cannot send more commands.但后来我无法发送更多命令。

This is what I am using at the moment (mostly from here ):这是我目前正在使用的(主要来自这里):

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);
        }
    }
}

I also tried reading the standard output stream synchronously, but with the same result.我还尝试同步读取标准输出流,但结果相同。 Any method call on the output stream block indefinitely until the input stream is closed - even Peek() and EndOfStream .对输出流的任何方法调用都会无限期地阻塞,直到输入流关闭 - 甚至是Peek()EndOfStream

Is there any way to communicate with the other process in a full duplex kind of way?有没有办法以全双工方式与其他进程通信?

I tried to reproduce your problem with a small test suite of my own.我试图用我自己的一个小型测试套件重现您的问题。 Instead of using event handlers I do it in the most trivial way I could conceive: Synchronously.我没有使用事件处理程序,而是以我能想到的最简单的方式做到这一点:同步。 This way no extra complexity is added to the problem.这样就不会给问题增加额外的复杂性。

Here my little "echoApp" I wrote in rust, just for the giggles and also to have a chance to run into the eternal line termination wars problem ( \\n vs \\r vs \\r\\n ).在这里,我用 rust 写了我的小“echoApp”,只是为了笑声,也有机会遇到永恒的线路终止战争问题( \\n vs \\r vs \\r\\n )。 Depending on the way your command line application is written, this could indeed be one of your problems.根据您编写命令行应用程序的方式,这确实可能是您的问题之一。

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;
            }
        }
    }
}

And - being a lazy bone who does not like creating a solution for such a small test, I used F# instead of C# for the controlling side - it is easy enough to read I think:而且 - 作为一个懒惰的人,不喜欢为这样一个小测试创建解决方案,我使用 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

Executing this in f# interactive (CTRL-A ALT-Enter in Visual Studio) yields:在 f# Interactive 中执行此操作(Visual Studio 中的 CTRL-A ALT-Enter)会产生:

val echoPath : string = "E:\\R\\rustic\\echo\\echoApp\\target\\debug\\echoApp.exe" val echoPath : string = "E:\\R\\rustic\\echo\\echoApp\\target\\debug\\echoApp.exe"

val createControlledProcess : path:string -> Process val createControlledProcess : path:string -> Process

val startupControlledProcess : p:Process -> unit val startupControlledProcess : p:Process -> unit

val shutdownControlledProcess : p:Process -> unit val shutdownControlledProcess : p:Process -> unit

val interact : p:Process -> arg:string -> string val 交互:p:Process -> arg:string -> string

val p : Process = System.Diagnostics.Process val p : Process = System.Diagnostics.Process

val results : string list = val 结果:字符串列表 =

["0: Hello"; ["0: 你好"; "1: World"; “1:世界”; "2: Whatever"; “2:随便”; "3: floats"; “3:浮动”; "4: your"; “4:你的”; "5: boat"] “5:船”]

val it : unit = ()验证它:单位 = ()

I could not reproduce any blocking or deadlocks etc. So, in your case I would try to investigate if maybe your NewLine property needs some tweaking (see function startupControlledProcess . If the controlled application does not recognize an input as a line, it might not respond, still waiting for the rest of the input line and you might get the effect you have.我无法重现任何阻塞或死锁等。因此,在您的情况下,我会尝试调查您的NewLine属性是否需要进行一些调整(请参阅函数startupControlledProcess 。如果受控应用程序无法将输入识别为一行,则它可能不会响应, 仍在等待输入行的其余部分,您可能会得到您所拥有的效果。

process.BeginOutputReadLine();

Doesn't work like expected, because it waits until output stream will be closed, which will happen when process will end, and process will end when its input stream will be closed.不像预期的那样工作,因为它一直等到输出流关闭,这将在进程结束时发生,而在其输入流将关闭时进程将结束。 As workaround just use combinations of process.StandardOutput.ReadLine() and asynchronous made by yourself作为解决方法,只需使用process.StandardOutput.ReadLine()和自己制作的异步的组合

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM