Seasons.NET

ちょっとした技術ブログです

ProcessでCygwinを操作したい!!(2)

今まであれこれと試した結果、ReadLine()の同期処理では、
出力がくるまで、ブロックしてしまい、結果プロセスに残ったりと
不具合がでていたので、それならば、非同期通信でエラーと出力を取ってしまおうという方針に方向転換。

下のテキストボックスにコマンドを改行コードで区切って定義して
あげれば、コマンド実行でcygwin上で実行する。
ちなみにデフォルトでは、ホームフォルダに移動し、main.cをコンパイル&実行&出力を取得する。

f:id:Seasons:20060524024131j:image

以下、コード。
非同期通信でそのままテキストに出力せず、いったんリストに
格納し、lockを使ってスレッドで常に監視しながら出力している。
あと、ここにサンプルコードをアーカイブしたもの。
※ダウンロード

これを使えば色々おもしろいことが出来ると思います。

  1  using System;
  2  using System.Collections.Generic;
  3  using System.ComponentModel;
  4  using System.Data;
  5  using System.Drawing;
  6  using System.Text;
  7  using System.Text.RegularExpressions;
  8  using System.Windows.Forms;
  9  using System.Diagnostics;
 10  using System.IO;
 11  using System.Threading;
 12
 13  namespace PipeSample1
 14  {
 15      public partial class Form1 : Form
 16      {
 17          private bool processStart_      = false;
 18          private Process process_        = null;
 19          private List<string> cmdData    = new List<string>();
 20          private const string cygwinPath = @"C:\\cygwin\\bin\\bash.exe";
 21
 22          public Form1()
 23          {
 24              InitializeComponent();
 25          }
 26          private void processStart()
 27          {
 28              if (processStart_ == false)
 29              {
 30                  process_ = new Process();
 31                  // プロセス実行して、パイプで出力内容を取得したいときは、これ実行パスを指定してください
 32                  ProcessStartInfo pifo = new ProcessStartInfo(cygwinPath);
 33
 34                  // ストリーム設定
 35                  pifo.UseShellExecute        = false;
 36                  pifo.RedirectStandardOutput = true;
 37                  pifo.RedirectStandardInput  = true;
 38                  pifo.RedirectStandardError  = true;
 39                  // ログイン情報
 40                  pifo.Arguments = "--login -i";
 41                  // ワーキングディレクトリ
 42                  //pifo.WorkingDirectory = Directory.GetCurrentDirectory();
 43                  // ウィンドウを表示しない
 44                  pifo.CreateNoWindow = true;
 45                  pifo.WindowStyle    = ProcessWindowStyle.Hidden;
 46                  process_.StartInfo  = pifo;
 47                  // ハンドラを設定
 48                  process_.ErrorDataReceived  += new DataReceivedEventHandler(CProcessOutputer.IOErrorDataHandler);
 49                  process_.OutputDataReceived += new DataReceivedEventHandler(CProcessOutputer.IOOutDataHandler);
 50                  CProcessOutputer.OutputTextBox = CmdOutputText;
 51                  CProcessOutputer.OutputThreadStart();
 52                  // プロセススタート(bash.exe)
 53                  processStart_ = true;
 54                  process_.Start();
 55                  // 非同期通信開始 -----> プロセスを起動してから呼ぶ
 56                  process_.BeginErrorReadLine();
 57                  process_.BeginOutputReadLine();
 58              }
 59         }
 60          /// <summary>
 61          /// プロセスを終了する
 62          /// </summary>
 63          /// <remarks>Disposeでコールしてください</remarks>
 64          private void Exit()
 65          {
 66              // 出力スレッド終了
 67              CProcessOutputer.Abort();
 68              // プロセス終了
 69              if (process_ != null)
 70              {
 71                  process_.Close();
 72                  process_ = null;
 73              }
 74          }
 75          /// <summary>
 76          /// コマンド実行
 77          /// </summary>
 78          /// <param name="sender"></param>
 79          /// <param name="e"></param>
 80          private void CmdExec_Click(object sender, EventArgs e)
 81          {
 82              // コマンド実行
 83              if( processStart_ == true )
 84              {
 85                  // コマンドパケットを生成
 86                  foreach( string cmd in CmdText.Lines )
 87                  {
 88                      cmdData.Add( cmd );
 89                  }
 90                  CmdOutputText.Clear();
 91                  // コマンドを実行
 92                  foreach (string cmd in cmdData)
 93                  {
 94                      process_.StandardInput.WriteLine(cmd);
 95                  }
 96                  cmdData.Clear();
 97              }
 98          }
 99          /// <summary>
100          /// ビルドパス
101          /// </summary>
102          /// <param name="sender"></param>
103          /// <param name="e"></param>
104          /// <remarks>今は、機能していない</remarks>
105          private void buildpath_Click(object sender, EventArgs e)
106          {
107              FolderBrowserDialog fd = new FolderBrowserDialog();
108
109              if (fd.ShowDialog() == DialogResult.OK)
110              {
111                  textBox2.Text = fd.SelectedPath;
112              }
113          }
114          /// <summary>
115          /// フォームロード時
116          /// </summary>
117          /// <param name="sender"></param>
118          /// <param name="e"></param>
119          private void Form1_Load(object sender, EventArgs e)
120          {
121              processStart_ = false;
122              processStart();
123          }
124
125      }
126      /// <summary>
127      /// プロセスアウトプッター
128      /// </summary>
129      class CProcessOutputer
130      {
131          /// <summary>
132          /// 受信パケットリスト
133          /// </summary>
134          private static List<string> outputData_ = new List<string>();
135          /// <summary>
136          /// 出力テキストボックス
137          /// </summary>
138          private static TextBox outputTextBox_ = null;
139          /// <summary>
140          /// プロセス終了フラグ
141          /// </summary>
142          private static bool processAbort_ = false;
143          /// <summary>
144          /// 出力管理スレッド
145          /// </summary>
146          private static Thread outThread_ = null;
147          /// <summary>
148          /// スケープシーケンスの出力をマッチしないで取得
149          /// そのままデータを受信すると、エスケープシーケンスまで取得するので
150          /// それを省いて受信するため
151          /// </summary>
152          private static Regex outPutRegex = new Regex(@"^[-_\w/)]+.+");
153
154          /// <summary>
155          /// 出力スレッド開始
156          /// </summary>
157          public static void OutputThreadStart()
158          {
159              outThread_ = new Thread(new ThreadStart(processOutputThread));
160              outThread_.Start();
161          }
162          /// <summary>
163          /// 出力テキストボックス設定用
164          /// </summary>
165          public static TextBox OutputTextBox
166          {
167              set{
168                  outputTextBox_ = value;
169              }
170              get
171              {
172                  return outputTextBox_;
173              }
174          }
175          /// <summary>
176          /// エラー時出力イベント
177          /// </summary>
178          /// <param name="sendingProcess">プロセス</param>
179          /// <param name="errLine">エラー出力文字列</param>
180          public static void IOErrorDataHandler(object sendingProcess,  DataReceivedEventArgs errLine)
181          {
182              if (!String.IsNullOrEmpty(errLine.Data))
183              {
184                  setOutputPacket(errLine);
185              }
186          }
187          /// <summary>
188          /// 標準出力時のイベント
189          /// </summary>
190          /// <param name="sendingProcess">プロセス</param>
191          /// <param name="errLine">エラー出力文字列</param>
192          public static void IOOutDataHandler(object sendingProcess,  DataReceivedEventArgs outLine)
193          {
194              if (!String.IsNullOrEmpty(outLine.Data))
195              {
196                  setOutputPacket(outLine);
197              }
198          }
199          /// <summary>
200          /// 出力パケットセット
201          /// </summary>
202          /// <param name="outLine">出力データ</param>
203          private static void setOutputPacket(DataReceivedEventArgs outLine)
204          {
205              if (outPutRegex.IsMatch(outLine.Data))
206              {
207                  lock (outputData_)
208                  {
209                      outputData_.Add(outLine.Data);
210                  }
211              }
212          }
213          /// <summary>
214          /// スレッド終了
215          /// </summary>
216          public static void Abort()
217          {
218              if (outThread_ == null)
219                  return;
220
221              if (outThread_.IsAlive)
222              {
223                  processAbort_ = true;
224                  outThread_.Join();
225              }
226          }
227          /// <summary>
228          /// データ出力管理スレッド
229          /// </summary>
230          private static void processOutputThread()
231          {
232              while (true)
233              {
234                  Thread.Sleep(10);
235
236                  // 終了
237                  if (processAbort_ == true)
238                  {
239                      break;
240                  }
241                  // テキストボックスがnull
242                  if (outputTextBox_ == null)
243                  {
244                      continue;
245                  }
246                  lock (outputData_)
247                  {
248                      // セットされているデータを全て出力
249                      foreach( string text in outputData_ )
250                      {
251                          outputTextBox_.AppendText(text+"\n");
252                      }
253                      // クリアにする
254                      outputData_.Clear();
255                  }
256              }
257          }
258      }
259
260  }