如何通过手动输入流将命令提供给cmd.exe进程?

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

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

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

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

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

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

因此,我决定将好的部分结合起来,而且,当我意识到,为了保持两个循环工作并等待我必须使用线程,我知道我正在找麻烦。 多年来我没有做过……

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

但它不起作用。

List batch = new List(); public volatile string output = "+"; public volatile string outputErr = "-"; Process CMD; Task cmdTask; volatile Queue cmdQueue = new Queue(); 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,输入流,输出流,线程,锁或其中的所有部分轮流.. ??

我终于搞定了。 我在代码示例中描述的不稳定行为的原因是没有以异步方式访问3个流。

为了纠正我丢弃了processOutput函数,并将其替换为进程本身触发的两个调用。 MS文档在这里给出了一个很好的例子

我还使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); } } 

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

  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这样的好老命令需要.. 😉