C# 調用外部進程的類,網上可以搜出很多來,為什麼要再寫一遍,實在是因為最近從網上拷貝了一個簡單的例程用到項目中,運行有問題,後來研究了半天,才解決了這些問題。於是打算寫這麼一篇博文,一來說說調用一個外部進程這麼簡單的一件事究竟會有哪些問題,二來也希望我寫的這個相對比較完整的類可以為軟件開發的同道們節約一些腦細胞,以便集中優勢兵力解決那些真正高深復雜的軟件問題。
在開始正題之前,我們先來看一看網上比較常見的執行外部進程的函數
private string RunCmd(string command)
{
//例Process
Process p = new Process();
p.StartInfo.FileName = "cmd.exe"; //確定程序名
p.StartInfo.Arguments = "/c " + command; //確定程式命令行
p.StartInfo.UseShellExecute = false; //Shell的使用
p.StartInfo.RedirectStandardInput = true; //重定向輸入
p.StartInfo.RedirectStandardOutput = true; //重定向輸出
p.StartInfo.RedirectStandardError = true; //重定向輸出錯誤
p.StartInfo.CreateNoWindow = true; //設置置不顯示示窗口
p.Start(); //00
//p.StandardInput.WriteLine(command); //也可以用這種方式輸入入要行的命令
//p.StandardInput.WriteLine("exit"); //要得加上Exit要不然下一行程式
return p.StandardOutput.ReadToEnd(); //輸出出流取得命令行結果果
}
這個方法應該是比較常見的調用外部進程的方法,我以前也一直是這樣調用外部進程的,也沒有碰到過什麼問題。但這次調用的外部進程比較特殊,用這種方法調用就出現了兩個問題。
第一個問題是這個被調用的外部進程有時候會出現異常,出現異常後Windows會彈出錯誤報告框,程序於是吊死在那裡,必須手工干預。這個問題比較好解決,程序中設置一下注冊表搞定。
第二個問題是調用這個外部進程(是一個控制台進程)後,程序會阻塞在p.StandardOutput.ReadToEnd();這一句,永遠無法出來,被調用的那個控制台程序也被吊死。但該控制台進程在CMD 中是可以正常執行的。後來看來一些資料才發現原來原因是出在該控制台程序控制台輸出大量字符串,管道重定向後,調用程序沒有及時將管道中的輸出數據取出,結果導致管道被阻塞,程序吊死。在這裡還有另外一個問題,雖然這次沒有遇到,但網上有其他人遇到,就是錯誤信息管道不及時取出數據,也會被阻塞,而且如果要同時取出兩個管道的數據,必須要利用一個輔助線程才能實現。
問題講完了,下面給出這個類的完整代碼
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
namespace Laboratory.Process
{
class ReadErrorThread
{
System.Threading.Thread m_Thread;
System.Diagnostics.Process m_Process;
String m_Error;
bool m_HasExisted;
object m_LockObj = new object();
public String Error
{
get
{
return m_Error;
}
}
public bool HasExisted
{
get
{
lock (m_LockObj)
{
return m_HasExisted;
}
}
set
{
lock (m_LockObj)
{
m_HasExisted = value;
}
}
}
private void ReadError()
{
StringBuilder strError = new StringBuilder();
while (!m_Process.HasExited)
{
strError.Append(m_Process.StandardError.ReadLine());
}
strError.Append(m_Process.StandardError.ReadToEnd());
m_Error = strError.ToString();
HasExisted = true;
}
public ReadErrorThread(System.Diagnostics.Process p)
{
HasExisted = false;
m_Error = "";
m_Process = p;
m_Thread = new Thread(new ThreadStart(ReadError));
m_Thread.Start();
}
}
class RunProcess
{
private String m_Error;
private String m_Output;
public String Error
{
get
{
return m_Error;
}
}
public String Output
{
get
{
return m_Output;
}
}
public bool HasError
{
get
{
return m_Error != "" && m_Error != null;
}
}
public void Run(String fileName, String para)
{
StringBuilder outputStr = new StringBuilder();
try
{
//disable the error report dialog.
//reference: http://www.devcow.com/blogs/adnrg/archive/2006/07/14/Disable-Error-Reporting-Dialog-for-your-application-with-the-registry.aspx
Microsoft.Win32.RegistryKey key;
key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"software\microsoft\PCHealth\ErrorReporting\", true);
int doReport = (int)key.GetValue("DoReport");
if (doReport != 0)
{
key.SetValue("DoReport", 0);
}
int showUI = (int)key.GetValue("ShowUI");
if (showUI != 0)
{
key.SetValue("ShowUI", 0);
}
}
catch
{
}
m_Error = "";
m_Output = "";
try
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = fileName;
p.StartInfo.Arguments = para;
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();
ReadErrorThread readErrorThread = new ReadErrorThread(p);
while (!p.HasExited)
{
outputStr.Append(p.StandardOutput.ReadLine()+"\r\n");
}
outputStr.Append(p.StandardOutput.ReadToEnd());
while (!readErrorThread.HasExisted)
{
Thread.Sleep(1);
}
m_Error = readErrorThread.Error;
m_Output = outputStr.ToString();
}
catch (Exception e)
{
m_Error = e.Message;
}
}
}
}