半斤八两的程序员

.net默默无语的追随者.
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

C#下写GUI - 管道操作 - 读取标准输出流 - 截获shell程序输出

Posted on 2009-03-22 18:49  炸弹  阅读(1035)  评论(0)    收藏  举报

这是个老话题了,可是折磨了我N个各月,我太笨了。某天终于很灵异地成功了,苦心人,天不负……

现在还有太多的程序用命令行(为啥都喜欢用呢,看起来很professional?),可整天敲参数、看黑白屏幕总不太爽吧?

GUI作用就是把这种“黑白屏幕”变成图形界面,看起来多舒服,是不是?

最初这个问题是在x264编码器出现的。这是个命令行程序,想写个GUI。我真是自不量力呀,刚接触程序没几天就敢充胖子,一上来就碰壁,求google大神,有所收获。不过我模仿就是实现不了,太笨了。

终于好了,有两个方法:一个是用StreamReader主动读取;另一个是用异步读取,应用回调函数。

个人认为前一种适合内容比较大多的情况,后一种适合比较少的情况。

先说第一种吧,

 先引用需要的命名空间

1 using System.Diagnostics;
2 using System.Threading;
然后添加一个方法,我直接用了Button,触发整个代码,下面是一些类级的声明

1 Process p;
2 delegate void chandle(string s);

        private void button1_Click(object sender, EventArgs e)
        {
            Thread tt 
= new Thread(new ThreadStart(t));
            tt.Start();
        }

在这里,新建了一个线程,我可不喜欢在主线程上跑,影响美观,下面写t()这个方法。

 1         private void t()
 2         {
 3             p = new Process();
 4             p.StartInfo.FileName = "azid.exe";
 5             p.StartInfo.Arguments = @"I:\kn\m12\m12..ac3 I:\kn\m12\au2.wav";
 6 
 7             p.StartInfo.CreateNoWindow = true;
 8             p.StartInfo.UseShellExecute = false;
 9             p.StartInfo.RedirectStandardError = true;
10 
11           
12            
13             p.Start();
14            
15             Thread rt = new Thread(new ThreadStart(read));
16             rt.Start();
17             p.WaitForExit();
18            
19             p.Dispose(); 
20         }
21 

在这里,创建一个Process类的实例,然后对StartInfo属性进行设置,主要是为了不要让他显示命令行窗口,并且把输出流RedirectStandardError导出,

这里多说一句,我这个程序azid的输出都被塞到了StandardError(标准错误输出)里面,貌似x264也是,你要注意一下你的程序输出是在标准输出还是错误输出,依次尝试一下即可,之后再创建一个线程,用来运行我们的读取函数,具体关系看下面这张涂鸦。

下面写读取函数read()

1         private void read()
2         {
3             StreamReader sr = p.StandardError;
4             while (!p.HasExited)
5             {
6                 Invoke(new chandle(ct), sr.ReadLine());
7                 Thread.Sleep(20);
8             }
9         }

这里用Invoke来控制主界面上的控件,因为这里是线程rt,不能直接操作主线程厄,我在这里弱智了N久,一直研究怎么做线程同步,其实是迷失了方向,偶然才发现Invoke方法的强大。

这里挂接的委托ct用来更新主界面的textbox,显示读取出来的内容,把sr.ReadLine()做参数传进去。

 

1  private void ct(string s)
2   {
3   textBox1.AppendText(s.Trim() + "\n");
4   textBox1.SelectionStart = textBox1.Text.Length - 1;
5   Application.DoEvents();
6  }
就是这样了哈……于是就可以工作了,不过这个程序有点问题,while (!p.HasExited)这里的条件有点单薄,容易异常掉,我比较懒



 下面说说异步读取,这个好像比上面的简单。

 1         Process p;
 2         delegate void chandle(string s);
 3 
 4 
 5         private void button1_Click(object sender, EventArgs e)
 6         {
 7             Thread tt = new Thread(new ThreadStart(t));
 8             tt.Start();
 9         }
10         private void t()
11         {
12             p = new Process();
13             p.StartInfo.FileName = "azid.exe";
14             p.StartInfo.Arguments = @"I:\kn\m12\m12..ac3 I:\kn\m12\au2.wav";
15             p.ErrorDataReceived += new DataReceivedEventHandler(p_ErrorDataReceived);//这里多了这句
16             p.StartInfo.CreateNoWindow = true;
17             p.StartInfo.UseShellExecute = false;
18             p.StartInfo.RedirectStandardError = true;
19             p.Start(); 
20             p.BeginErrorReadLine(); //这里多了这句
21             p.WaitForExit();
22           
23             p.Dispose(); 
24         }
25 
和第一种方法差不多,只不过多了2句,第一句为ErrorDataReceived添加一个事件处理函数,就是那个回调函数,第二句开始读取

 

1   void p_ErrorDataReceived(object sender, DataReceivedEventArgs e)
2         {
3             if (!string.IsNullOrEmpty(e.Data))
4             {
5                // 做点工作
6             }
7         }

 程序每向输出流写入一行时,触发一次这个事件,e.Data属性可以获得这一行数据,然后怎么处理就看你了。

这个方法如果你的输出流行数很多,那就要不断地调用这个函数,很费资源orz,到底用哪个,根据情况判断吧,当然你也可以2个交错使用,在msdn上有写限制条件

地址在这里:http://msdn.microsoft.com/zh-cn/library/system.diagnostics.process.standarderror(VS.80).aspx

風之谷