在WinForm进程中,如何检测命令行子进程何时需要输入?

发布于 2024-12-05 10:08:05 字数 2824 浏览 0 评论 0原文

在调用第三方命令行工具的 WinForm 应用程序中,有时该工具可能会等待用户输入,例如询问是否应该覆盖文件:

printf("%s already exists, overwrite?: <Y>es, <N>o, <A>ll, <Q>uit?",FName);
for ( ; ; )
    switch ( toupper(getch()) ) {
        case 'A':
            YesToAll=true;
        case '\r': 
        case 'Y':
            remove(FName);
            return true;
        case 0x1B: 
        case 'Q':
            printf("quit\n"); exit(-1);
        case 'N':                       
            return false;
    }

当发生这种情况时,我想显示来自 printf() 和对话框中的选项,并将按钮单击重定向为流程的输入。它可能涉及使用 System.Diagnostics.Process.StandardInput 发送输入。但如果不知道工具何时需要输入,我就不知道何时在 GUI 中做出相应的反应。当进程处于这个 for 循环中时,我的 WinForm 进程将冻结。

编辑:这是通过在另一个线程中启动进程来解锁 UI 的代码,但是如果我选择的文件将导致工具询问覆盖选项,我仍然无法读取输出。 proc_OutputDataReceivedEDIT2:或 proc.StandardOutput.BaseStream.BeginRead 中的 readStdOut)永远不会被调用,除非该工具不要求输入)。

    private BackgroundWorker worker = new BackgroundWorker();

    private void fileChosenHandler(object sender, EventArgs e)
    {
        OpenFileDialog dialog = sender as OpenFileDialog;
        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        worker.RunWorkerAsync(dialog.FileName);
    }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        string exePath = @"F:\test\test.exe";
        Process proc = new Process();
        proc.StartInfo.FileName = exePath; 
        proc.StartInfo.Arguments = "\"" + (string)e.Argument + "\""; 
        proc.StartInfo.UseShellExecute = false; 
        proc.StartInfo.CreateNoWindow = true;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);

        proc.Start();

        // method 1: read lines
        //proc.BeginOutputReadLine();
        // method 2: read characters
        proc.StandardOutput.BaseStream.BeginRead(stdOutBuffer, 0, stdOutBuffer.Length, readStdOut, proc);

        proc.WaitForExit();

    }

    private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        MessageBox.Show("Output: " + e.Data);
    }

    private byte[] stdOutBuffer = new byte[20]; 

    private void readStdOut(IAsyncResult result)
    {
        Process proc = result.AsyncState as Process;

        int bytesNumber = proc.StandardOutput.BaseStream.EndRead(result);
        if (bytesNumber != 0)
        {
            string text = System.Text.Encoding.ASCII.GetString(stdOutBuffer, 0, bytesNumber);
            MessageBox.Show("Output: " + text);
        }

        // set up the callback again
        proc.StandardOutput.BaseStream.BeginRead(stdOutBuffer, 0, stdOutBuffer.Length, readStdOut, proc);
    }

知道如何做到这一点吗?谢谢!

In a WinForm application that calls a third-party command line tool, there may be a time when the tool is expecting user input, such as asking if it should overwrite a file:

printf("%s already exists, overwrite?: <Y>es, <N>o, <A>ll, <Q>uit?",FName);
for ( ; ; )
    switch ( toupper(getch()) ) {
        case 'A':
            YesToAll=true;
        case '\r': 
        case 'Y':
            remove(FName);
            return true;
        case 0x1B: 
        case 'Q':
            printf("quit\n"); exit(-1);
        case 'N':                       
            return false;
    }

When that happens, I want to display the message from printf() and the options in a dialog box, and redirect the button click as input to the process. It probably involves using System.Diagnostics.Process.StandardInput to send the input. But without knowing when exactly the tool will be expecting input, I would have no idea when to react accordingly in the GUI. When the process is in this for-loop, my WinForm process will just freeze.

EDIT: Here's the code that unblocks the UI by starting the process in another thread, however I still could not read the output, if the file I select will cause the tool to ask overwriting options. proc_OutputDataReceived (EDIT2: or readStdOut in proc.StandardOutput.BaseStream.BeginRead's case) will never be called , unless the tool does not ask for input).

    private BackgroundWorker worker = new BackgroundWorker();

    private void fileChosenHandler(object sender, EventArgs e)
    {
        OpenFileDialog dialog = sender as OpenFileDialog;
        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        worker.RunWorkerAsync(dialog.FileName);
    }

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        string exePath = @"F:\test\test.exe";
        Process proc = new Process();
        proc.StartInfo.FileName = exePath; 
        proc.StartInfo.Arguments = "\"" + (string)e.Argument + "\""; 
        proc.StartInfo.UseShellExecute = false; 
        proc.StartInfo.CreateNoWindow = true;
        proc.StartInfo.RedirectStandardOutput = true;
        proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);

        proc.Start();

        // method 1: read lines
        //proc.BeginOutputReadLine();
        // method 2: read characters
        proc.StandardOutput.BaseStream.BeginRead(stdOutBuffer, 0, stdOutBuffer.Length, readStdOut, proc);

        proc.WaitForExit();

    }

    private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        MessageBox.Show("Output: " + e.Data);
    }

    private byte[] stdOutBuffer = new byte[20]; 

    private void readStdOut(IAsyncResult result)
    {
        Process proc = result.AsyncState as Process;

        int bytesNumber = proc.StandardOutput.BaseStream.EndRead(result);
        if (bytesNumber != 0)
        {
            string text = System.Text.Encoding.ASCII.GetString(stdOutBuffer, 0, bytesNumber);
            MessageBox.Show("Output: " + text);
        }

        // set up the callback again
        proc.StandardOutput.BaseStream.BeginRead(stdOutBuffer, 0, stdOutBuffer.Length, readStdOut, proc);
    }

Any idea how to do this? Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

北斗星光 2024-12-12 10:08:05

从 System.Diagnostics.StandardOutput 读取(如果您使用阻塞读取,则必须在单独的线程中执行),直到找到该字符串的匹配项,然后显示消息框并发送该字符根据用户的选择到进程的StandardInput


快速总结我们尝试过的事情:

  • 使用 BeginOutputReadLine 进行“正常”异步读取 ->肯定会失败,因为来自应用程序的消息不是以 '\n' 终止的;
  • 以 1 字节块异步读取 ->似乎失败,因为应用程序没有刷新缓冲区;
  • fflush(stdout) 添加到 C 应用程序 + 之前的方法:成功!显然,程序在 getch() 之前没有刷新输出缓冲区。

有趣的是,这在使用标准 iostream 的 C++ 应用程序中可能不会发生,因为 cin 与 cout 相关联,并且在对 cin 进行任何输入操作之前code> 发生时 cout 会自动刷新。我认为 stdin/stdout 也发生这样的事情是合理的,但我似乎无法在标准中找到任何对它的引用(也是事实聊天) getch() 是非标准的,与其他 IO 函数不同,可能与无缓冲有关)。

更多信息请参阅评论。 :)

Read from System.Diagnostics.StandardOutput (if you use a blocking read you'll have to do it in a separated thread) until you find a match for that string, then display your messagebox and send the character to the StandardInput of the process according to the user choice.


Quick summary of the things we tried:

  • "normal" asynchronous read with BeginOutputReadLine -> surely fails because the message from the application isn't terminated with a '\n';
  • asynchronous read in 1-byte blocks -> seems to fail because the application isn't flushing the buffer;
  • adding a fflush(stdout) to the C application + previous approach: success! Apparently the program wasn't flushing the output buffer before the getch().

Interestingly, this probably wouldn't happen in a C++ application using the standard iostreams, since cin is tied to cout, and before any input operation on cin happens cout is automatically flushed. I thought it was reasonable that something like this happened also for stdin/stdout, but I can't seem to find any reference to it into the standard (also the fact chat getch() is nonstandard and differs from the other IO functions in being unbuffered may be related).

Fore more info see the comments. :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文