如何从命令中获取输出以实时显示在Form的控件中?

Cyb*_*ops 1 .net c# process winforms

从网络上的各种来源中,我整理了以下代码,用于通过CMD.exe和捕获来自STDOUT和的输出来执行命令STDERR。

public static class Exec
{
    public delegate void OutputHandler(String line);

    // <summary>
    /// Run a command in a subprocess
    /// </summary>
    /// <param name="path">Directory from which to execute the command</param>
    /// <param name="cmd">Command to execute</param>
    /// <param name="args">Arguments for command</param>
    /// <param name="hndlr">Command output handler (null if none)</param>
    /// <param name="noshow">True if no windows is to be shown</param>
    /// <returns>Exit code from executed command</returns>
    public static int Run(String path, String cmd, String args,
                          OutputHandler hndlr = null, Boolean noshow = true)
    {
        // Assume an error
        int ret = 1;
        // Create a process
        using (var p = new Process())
        {
            // Run command using CMD.EXE
            // (this way we can pipe STDERR to STDOUT so they can get handled together)
            p.StartInfo.FileName = "cmd.exe";
            // Set working directory (if supplied)
            if (!String.IsNullOrWhiteSpace(path)) p.StartInfo.WorkingDirectory = path;
            // Indicate command and arguments
            p.StartInfo.Arguments = "/c \"" + cmd + " " + args + "\" 2>&1";
            // Handle noshow argument
            p.StartInfo.CreateNoWindow = noshow;
            p.StartInfo.UseShellExecute = false;
            // See if handler provided
            if (hndlr != null)
            {
                // Redirect STDOUT and STDERR
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardError = true;
                // Use custom event handler to capture output
                using (var outputWaitHandle = new AutoResetEvent(false))
                {
                    p.OutputDataReceived += (sender, e) =>
                    {
                        // See if there is any data
                        if (e.Data == null)
                        {
                            // Signal output processing complete
                            outputWaitHandle.Set();
                        }
                        else
                        {
                            // Pass string to string handler
                            hndlr(e.Data);
                        }
                    };
                    // Start process
                    p.Start();
                    // Begin async read
                    p.BeginOutputReadLine();
                    // Wait for process to terminate
                    p.WaitForExit();
                    // Wait on output processing complete signal
                    outputWaitHandle.WaitOne();
                }
            }
            else
            {
                // Start process
                p.Start();
                // Wait for process to terminate
                p.WaitForExit();
            }
            // Get exit code
            ret = p.ExitCode;
        }
        // Return result
        return ret;
    }

    // <summary>
    /// Run a command in a subprocess and return output in a variable
    /// </summary>
    /// <param name="path">Directory from which to execute the command</param>
    /// <param name="cmd">Command to execute</param>
    /// <param name="args">Arguments for command</param>
    /// <param name="outp">Variable to contain the output</param>
    /// <returns>Exit code from executed command</returns>
    public static GetOutputReturn GetOutput(String path, String cmd, String args)
    {
        GetOutputReturn ret = new GetOutputReturn();
        ret.ReturnCode = Run(path, cmd, args, (line) =>
                             {
                               ret.Output.AppendLine(line);
                             });
        return ret;
    }
}

public class GetOutputReturn
{
    public StringBuilder Output = new StringBuilder();
    public int ReturnCode = 1;
}
Run Code Online (Sandbox Code Playgroud)

我可以通过以下三种不同方式在控制台应用程序中使用它:

static void Main(string[] args)
{
    int ret;
    Console.WriteLine("Executing dir with no capture and no window");
    ret = Exec.Run(@"C:\", "dir", "");
    Console.WriteLine("Execute returned " + ret);
    Console.WriteLine("Press enter to continue ...");
    Console.ReadLine();
    Console.WriteLine("Executing dir with no capture and window");
    ret = Exec.Run(@"C:\", "dir", "", null, false);
    Console.WriteLine("Execute returned " + ret);
    Console.WriteLine("Press enter to continue ...");
    Console.ReadLine();
    Console.WriteLine("Executing dir with capture and no window");
    var results = Exec.GetOutput(@"C:\", "dir", "");
    Console.WriteLine(results.Output.ToString());
    Console.WriteLine("Execute returned " + results.ReturnCode);
    Console.ReadLine();
    Console.WriteLine("Executing dir with real-time capture and no window");
    ret = Exec.Run(@"C:\", "dir", "", ShowString);
    Console.WriteLine("Execute returned " + ret);
}

public delegate void StringData(String str);

static void ShowString(String str)
{
    Console.WriteLine(str);
}

public delegate void StringData(String str);

static void ShowString(String str)
{
    Console.WriteLine(str);
}
Run Code Online (Sandbox Code Playgroud)

第一次运行不收集任何输出,仅显示退出代码。
第二次运行不收集任何输出,但显示窗口。
输出将实时显示在控制台窗口中的效果。
第三次运行使用GetOutput收集输出。
这样的结果是直到运行完成才出现输出。
上一次运行使用处理程序来接收和实时显示输出。
从外观上看,它看起来像第二轮,但完全不同。
对于接收到的每一行输出,将调用ShowString。
显示字符串仅显示字符串。
但是,它可以处理数据所需的任何操作。

我正在尝试调整上一次运行的时间,以便可以使用命令的输出实时更新文本框。我遇到的问题是如何在正确的背景下获得它(因为缺乏更好的期限)。因为OutputHandler是异步调用的,所以它必须使用该InvokeRequired/BeginInvoke/EndInvoke机制与UI线程同步。我对如何使用参数有一点问题。在我的代码中,textBox可能是选项卡控件中的几个,因为可能会发生多个背景“运行”。

到目前为止,我有这个:

private void btnExecute_Click(object sender, EventArgs e)
{
    // Get currently selected tab page
    var page = tcExecControl.SelectedTab;
    // Get text box (always 3rd control on the page)
    var txt = (TextBox)page.Controls[2];
    // Create string handler
    var prc = new Exec.OutputHandler((String line) =>
                  {
                      if (txt.InvokeRequired)
                          txt.Invoke(new MethodInvoker(() =>
                                     { txt.Text += line; }));
                          else txt.Text += line;
                   });
    // Command and arguments are always 1st and 2nd controls on the page
    var result = Exec.Run(@"C:\", page.Controls[0].Text, page.Controls[1], prc);                              
}
Run Code Online (Sandbox Code Playgroud)

但这似乎不起作用。我看不到txtBox的任何输出。
实际上,程序基本上挂在处理程序中。

如果我更改代码以使用GetOutput,然后将结果输出写入文本框,则一切正常。所以我知道我已经正确设置了命令。使用调试器,我可以在“ if(txt.InvokeRequired)”行上设置断点,并且看到输出的第一行正确显示。此时,代码采用if语句的真实路径,但是如果我在该txt.Text += line;行上设置断点,它将永远不会到达那里。

谁能帮我吗?我确定我想念什么。

Jim*_*imi 5

此示例中代码执行的简要说明。

首先将shell命令(cmd.exe)start /WAIT作为参数运行。几乎与以下功能相同/k:在没有任何特定任务的情况下启动控制台,等待发送命令时处理命令。

StandardOutput,StandardError并StandardInput都重定向,设置RedirectStandardOutput,RedirectStandardError和RedirectStandardInput所述的性质的ProcessStartInfo到true。

控制台输出流在写入时将引发OutputDataReceived 事件。它的内容可以从DataReceivedEventArgs的e.Data成员中读取。出于相同的目的,将使用其ErrorDataReceived事件。 (您可以对两个事件使用单个事件处理程序,但是经过一些测试,您可能会意识到这不是一个好主意。将它们分开可以避免一些奇怪的重叠,并可以轻松地将错误与正常输出区分开来)。
StandardError

StandardInput可以重定向,将其分配给StreamWriter流。
每次将字符串写入流时,控制台都会将该输入解释为要执行的命令。

此外,还指示该进程在终止时引发其Exited事件,并将其EnableRaisingEvents属性设置为true。当关闭进程时会引发
该Exited事件,因为Exit处理了一条命令,调用了.Close()方法或.Kill()方法。仅出于某种原因,当流程不再响应时,才应使用
该.Kill()方法。

由于我们需要将控制台输出传递给某些UI控件(RichTextBoxes在本示例中),因此必须将事件上下文与UI同步。
可以使用Process SynchronizingObject属性,将其设置为UI控件(容器Window可能是首选,因为有多个控件要同步),或者使用Control.BeginInvoke方法,可以在以下位置执行委托函数:控件的句柄所属的线程。
此处,代表委托的MethodInvoker用于此目的。


用于实例化Process并设置其属性和事件处理程序的核心函数:

using System;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;

StreamWriter stdin = null;

private void StartCmdProcess()
{
    ProcessStartInfo pStartInfo = new ProcessStartInfo {
        FileName = "cmd.exe",
        Arguments = "start /WAIT",
        WorkingDirectory = Environment.SystemDirectory,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        RedirectStandardInput = true,
        UseShellExecute = false,
        CreateNoWindow = true,
    };

    Process cmdProcess = new Process {
        StartInfo = pStartInfo,
        EnableRaisingEvents = true,
        // Test without and with this
        // When SynchronizingObject is set, no need to BeginInvoke()
        //SynchronizingObject = this
    };

    cmdProcess.Start();
    cmdProcess.BeginErrorReadLine();
    cmdProcess.BeginOutputReadLine();
    stdin = cmdProcess.StandardInput;

    cmdProcess.OutputDataReceived += (s, evt) => {
        if (evt.Data != null)
        {
            BeginInvoke(new MethodInvoker(() => {
                rtbStdOut.AppendText(evt.Data + Environment.NewLine);
                rtbStdOut.ScrollToCaret();
            }));
        }
    };

    cmdProcess.ErrorDataReceived += (s, evt) => {
        if (evt.Data != null) {
            BeginInvoke(new Action(() => {
                rtbStdErr.AppendText(evt.Data + Environment.NewLine);
                rtbStdErr.ScrollToCaret();
            }));
        }
    };

    cmdProcess.Exited += (s, evt) => {
        cmdProcess.Close();
        if (cmdProcess != null) {
            cmdProcess.Dispose();
        }
    };
}
Run Code Online (Sandbox Code Playgroud)


样本表格可以从PasteBin下载。

控制台实时重定向