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

从网络上的各种来源,我已经把下面的代码用于经由执行命令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

      

第一次运行不收集任何输出,只显示退出代码。
第二次运行不会收集任何输出,但会显示窗口。
这样做的效果是输出实时出现在控制台窗口中。
第三次运行使用 GetOutput 来收集输出。
这样做的效果是在运行完成之前不会出现输出。
最后一次运行使用处理程序实时接收和显示输出。
从外观上看,这看起来像第二次运行,但它非常不同。
对于接收到的每一行输出,调用 ShowString。
Show string 只是显示字符串。
然而,它可以对数据做任何它需要的事情。

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


慕运维8079593
浏览 245回答 1
1回答

潇湘沐

代码在此示例中执行的操作的简要说明:shell 命令 ( cmd.exe) 首先运行,start /WAIT用作参数。或多或少与以下功能相同/k:控制台在没有任何特定任务的情况下启动,在发送命令时等待处理命令。StandardOutput,StandardError并且StandardInput都被重定向,将ProcessStartInfo 的RedirectStandardOutput、RedirectStandardError和RedirectStandardInput属性设置为。true控制台输出流在写入时将引发OutputDataReceived事件;它的内容可以从DataReceivedEventArgs的e.Data成员中读取。将其ErrorDataReceived事件用于相同目的。 您可以对这两个事件使用单个事件处理程序,但是,经过一些测试后,您可能会意识到这可能不是一个好主意。将它们分开可以避免一些奇怪的重叠,并允许轻松区分错误与正常输出(注意,您可以找到写入错误流而不是输出流的程序)。StandardErrorStandardInput可以重定向,将其分配给StreamWriter流。每次将字符串写入流时,控制台都会将该输入解释为要执行的命令。此外,进程被指示在终止时引发它的Exited事件,将其EnableRaisingEvents属性设置为true。在Exited当该过程被关闭,因为引发事件Exit命令被处理或调用.Close()方法(或最终的.Kill()方法,应仅用于whcich仅当一个进程不响应了,对于一些原因)。由于我们需要将控制台输出传递给某些 UI 控件(RichTextBoxes在此示例中)并且 Process 事件在 ThreadPool 线程中引发,因此我们必须将此上下文与 UI 同步。这可以通过使用 Process SynchronizingObject属性、将其设置为父窗体或使用Control.BeginInvoke方法来完成,该方法将在控件句柄所属的线程上执行委托函数。在这里,代表委托的MethodInvoker用于此目的。用于实例化 Process 并设置其属性和事件处理程序的核心函数:using System;using System.Diagnostics;using System.IO;using System.Windows.Forms;StreamWriter stdin = null;public partial class frmCmdInOut : Form{&nbsp; &nbsp; Process cmdProcess = null;&nbsp; &nbsp; StreamWriter stdin = null;&nbsp; &nbsp; public frmCmdInOut() => InitializeComponent();&nbsp; &nbsp; private void MainForm_Load(object sender, EventArgs e)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; rtbStdIn.Multiline = false;&nbsp; &nbsp; &nbsp; &nbsp; rtbStdIn.SelectionIndent = 20;&nbsp; &nbsp; }&nbsp; &nbsp; private void btnStartProcess_Click(object sender, EventArgs e)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; btnStartProcess.Enabled = false;&nbsp; &nbsp; &nbsp; &nbsp; StartCmdProcess();&nbsp; &nbsp; &nbsp; &nbsp; btnEndProcess.Enabled = true;&nbsp; &nbsp; }&nbsp; &nbsp; private void btnEndProcess_Click(object sender, EventArgs e)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (stdin.BaseStream.CanWrite) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stdin.WriteLine("exit");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; btnEndProcess.Enabled = false;&nbsp; &nbsp; &nbsp; &nbsp; btnStartProcess.Enabled = true;&nbsp; &nbsp; &nbsp; &nbsp; cmdProcess?.Close();&nbsp; &nbsp; }&nbsp; &nbsp; private void rtbStdIn_KeyPress(object sender, KeyPressEventArgs e)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (e.KeyChar == (char)Keys.Enter) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (stdin == null) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rtbStdErr.AppendText("Process not started" + Environment.NewLine);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; e.Handled = true;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (stdin.BaseStream.CanWrite) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stdin.Write(rtbStdIn.Text + Environment.NewLine);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stdin.WriteLine();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // To write to a Console app, just&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // stdin.WriteLine(rtbStdIn.Text);&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rtbStdIn.Clear();&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; private void StartCmdProcess()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; var pStartInfo = new ProcessStartInfo {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;FileName = "cmd.exe",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Batch File Arguments = "/C START /b /WAIT somebatch.bat",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Test: Arguments = "START /WAIT /K ipconfig /all",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Arguments = "START /WAIT",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WorkingDirectory = Environment.SystemDirectory,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // WorkingDirectory = Application.StartupPath,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RedirectStandardOutput = true,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RedirectStandardError = true,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RedirectStandardInput = true,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; UseShellExecute = false,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; CreateNoWindow = true,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WindowStyle = ProcessWindowStyle.Hidden,&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; cmdProcess = new Process {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; StartInfo = pStartInfo,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; EnableRaisingEvents = true,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Test without and with this&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // When SynchronizingObject is set, no need to BeginInvoke()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //SynchronizingObject = this&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; cmdProcess.Start();&nbsp; &nbsp; &nbsp; &nbsp; cmdProcess.BeginErrorReadLine();&nbsp; &nbsp; &nbsp; &nbsp; cmdProcess.BeginOutputReadLine();&nbsp; &nbsp; &nbsp; &nbsp; stdin = cmdProcess.StandardInput;&nbsp; &nbsp; &nbsp; &nbsp; // stdin.AutoFlush = true;&nbsp; <- already true&nbsp; &nbsp; &nbsp; &nbsp; cmdProcess.OutputDataReceived += (s, evt) => {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (evt.Data != null)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BeginInvoke(new MethodInvoker(() => {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rtbStdOut.AppendText(evt.Data + Environment.NewLine);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rtbStdOut.ScrollToCaret();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; cmdProcess.ErrorDataReceived += (s, evt) => {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (evt.Data != null) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BeginInvoke(new Action(() => {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rtbStdErr.AppendText(evt.Data + Environment.NewLine);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rtbStdErr.ScrollToCaret();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }));&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; cmdProcess.Exited += (s, evt) => {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; stdin?.Dispose();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cmdProcess?.Dispose();&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; }}由于 StandardInput 已被重定向到 StreamWriter:stdin = cmdProcess.StandardInput;我们只需写入 Stream 以执行命令:stdin.WriteLine(["Command Text"]);示例表单可以从 PasteBin 下载。
打开App,查看更多内容
随时随地看视频慕课网APP