【问题标题】:How can I feed commands to cmd.exe process via an input stream manually?如何手动通过输入流向 cmd.exe 进程提供命令?
【发布时间】:2014-04-12 21:56:17
【问题描述】:

这个问题听起来有点密集。这是一个稍长的版本:

我需要让主循环等待用户输入,并且还有一个进程正在运行并等待用户输入要发送到的流中的输入。

完整故事:我正在构建一个 Cmd 模拟器,起初一切看起来都很好:用户输入命令,它被回显到输出区域,经过处理,StdOut 和 StdErrOut 被捕获并添加到输出 TextBox。

唯一的问题是,由于 cmd 进程是为每个命令单独创建和启动的,因此没有保留任何状态。既不是变量,也不是代码页,也不是工作目录等。

所以我决定发明一个小技巧:输入左括号或右括号开始和停止收集命令,而不是执行它们。在右括号之后,在 processBatch 方法中使用命令列表('batch'),通过其重定向输入将它们全部提供给 cmd 进程。工作得很好。

显然,唯一的问题是,现在我得到了状态但失去了立即响应,所以在批处理运行之前不会弹出任何错误。

所以我决定把好的部分结合起来,好吧,当我意识到要保持两个循环工作和等待时,我知道我正面临麻烦,我必须使用线程。我已经很多年没有做过了..

在布局中,我选择了 main() 循环等待用户输入,而 startCMDtask() 在任务中运行 startCMD()。这里扫描输入流,直到有数据,然后cmd进程处理它们..

但它不起作用。

List<string> batch = new List<string>();

public volatile string output = "+";
public volatile string outputErr = "-";

Process CMD;
Task cmdTask;

volatile Queue<string> cmdQueue = new Queue<string>();
volatile public bool CMDrunning = false;

这很好用

private void processBatch()
{
    Process p = new Process();
    ProcessStartInfo info = new ProcessStartInfo();
    info.FileName = "cmd.exe";
    info.RedirectStandardOutput = true;
    info.RedirectStandardError = true;
    info.RedirectStandardInput = true;
    info.UseShellExecute = false;

    p.StartInfo = info;
    p.Start();

    using (StreamWriter sw = p.StandardInput)
    {
        if (sw.BaseStream.CanWrite)
            foreach(string line in batch) sw.WriteLine(line);
    }
    output = "^"; outputErr = "~";
    try { output = p.StandardOutput.ReadToEnd(); } catch { }
    try { outputErr = p.StandardError.ReadToEnd(); } catch { }
    try { p.WaitForExit(); } catch { }
    tb_output.AppendText(output + "\r\n" + outputErr + "\r\n");
}

这些不完全,但几乎..

private void setupCMD()
{
    CMD = new Process();
    ProcessStartInfo info = new ProcessStartInfo();
    info.FileName = "cmd.exe";
     // info.Arguments = "/K";   // doesn't make a difference
    info.CreateNoWindow = true;
    info.RedirectStandardOutput = true;
    info.RedirectStandardError = true;
    info.RedirectStandardInput = true;
    info.UseShellExecute = false;
    CMD.StartInfo = info;
}


private void startCMDtask()
{
    var task = Task.Factory.StartNew(() => startCMD());
    cmdTask = task;
}


private void startCMD()
{
    try   { CMD.Start(); CMDrunning = true; } 
    catch { output = "Error starting cmd process.\r\n"; CMDrunning = false; }

    using (StreamWriter sw = CMD.StandardInput)
    {
        if (sw.BaseStream.CanWrite)
            do {  
                try 
                {
                    string cmd = cmdQueue.Dequeue();
                    if (cmd != null & cmd !="")
                    {
                        sw.WriteLine(cmd);
                        processOutputStreams();
                    }
                } 
                catch {} 
            } while (CMDrunning);
    }


private void processOutputStreams()
{
    string newOutput = ""; string newOutputErr = "";
    while (CMD.StandardOutput.Peek() > 0)
              newOutput += (char)(CMD.StandardOutput.Read());

    newOutput += "!?";    // at this point stdout is correctly captured  (1)  

    try {
      while (CMD.StandardError.Peek() > 0)    // from here execution jumps away (2)
      { newOutputErr += (char)(CMD.StandardError.Read()); }
    } catch { 
        newOutputErr = "?";   // never comes here
    }



    lock (output)    // no noticable difference
    lock (outputErr) //
    {                // if I jump here (3) from (1) the result is displayed
                     // but not if i comment out the 2nd while loop (2)
        if (newOutput != null & newOutput != "") output += newOutput + "\r\n";
        if (newOutputErr != null & newOutputErr != "") outputErr += newOutputErr + "\r\n";
    }
}

这是来自主线程中输入处理器的调用:

lock (cmdQueue) cmdQueue.Enqueue(cmd);

我不知道是哪一部分出了问题:进程、cmd shell、输入流、输出流、线程、锁或所有这些轮流......??

【问题讨论】:

  • 感谢连续投反对票!

标签: c# process cmd task streamwriter


【解决方案1】:

我终于让它工作了。我在代码示例中描述的异常行为的原因是 3 个流不是以异步方式访问的。

为了纠正我放弃了 processOutput 函数,并用进程本身触发的两个调用替换了它。 MS 文档给出了一个很好的例子here

我还使 StreamWriter 同步,它也为进程及其运行的整个任务提供数据。

这是新代码:

private void startCMDtask()
{
    var task = Task.Factory.StartNew(() => startCMD());
    cmdTask = task;
}

private async void startCMD()
{
    try   { CMD.Start(); CMDrunning = true; } 
    catch { cmdErrOutput.Append("\r\nError starting cmd process."); 
            CMDrunning = false; }

    CMD.BeginOutputReadLine();
    CMD.BeginErrorReadLine();

    using (StreamWriter sw = CMD.StandardInput)
    {

        if (sw.BaseStream.CanWrite)
            do {  
                try 
                {
                    string cmd = cmdQueue.Dequeue();
                    if (cmd != null & cmd !="")  await sw.WriteLineAsync(cmd);
                } 
                catch { } 
            }   while (CMDrunning);
        try   { CMD.WaitForExit(); } 
        catch { cmdErrOutput.Append("WaitForExit Error.\r\n"); }
    }
}

现在是这样设置流程的:

private void setupCMD()
{
    CMD = new Process();
    ProcessStartInfo info = new ProcessStartInfo();
    info.FileName = "cmd.exe";
    info.CreateNoWindow = true;
    info.RedirectStandardOutput = true;
    info.RedirectStandardError = true;
    info.RedirectStandardInput = true;
    info.UseShellExecute = false;

    CMD.OutputDataReceived += new DataReceivedEventHandler(cmdOutputDataHandler);
    CMD.ErrorDataReceived += new DataReceivedEventHandler(cmdErrorDataHandler);
    cmdOutput = new StringBuilder();
    cmdErrOutput = new StringBuilder();
    CMD.StartInfo = info;
}

这里是输出处理程序:

private static void cmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
    if (!String.IsNullOrEmpty(outLine.Data))
    {  // Add the text to the collected output.
        cmdOutput.Append(Environment.NewLine + outLine.Data);
    }
}

private static void cmdErrorDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
    if (!String.IsNullOrEmpty(outLine.Data))
    {  // Add the text to the collected error output.
        cmdErrOutput.Append(Environment.NewLine + outLine.Data);
    }
}

在用户输入处理结束时,这是输入队列的处理方式和输出获取方式:

    cmdUnDoStack.Push(cmd);
    Application.DoEvents();
    TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Factory.StartNew(() => updateOutputArea(uiScheduler));

使用这个小程序:

private void updateOutputArea(TaskScheduler uiScheduler)
{
    Task.Factory.StartNew(() =>
    {
        tb_output.AppendText(cmdOutput + "\r\n" + cmdErrOutput + "\r\n");
        cmdOutput.Clear();
        cmdErrOutput.Clear();
    }, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler);


    }

现在对于特殊处理,一些像 CLS 或 COLOR 这样的旧命令需要.. ;-)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-17
    • 2017-08-19
    相关资源
    最近更新 更多