C#からPythonスクリプトを同期的に実行する

#はじめに

これまでC#とPythonの連携について似たようなネタをいくつか書いてきた。

「[C# GUIアプリケーションからPythonスクリプトを実行する](https://qiita.com/kimisyo/items/0879fc9f1315e2abfd1f)」では、Pythonスクリプトを実行し、処理が終了するまで進捗状況を確認できるようにした。

「[C# GUIからPythonのコードを実行する](https://qiita.com/kimisyo/items/9a554c6021b9e88b81de)」では、Pythonスクリプトではなく、画面上からPythonコードを入力できるようにしてみた。

そして「[C# GUIからバックグラウンドでプロセスを実行し管理する](https://qiita.com/kimisyo/items/02cc7033447a23cad881)」では、Pythonを実行した後、終了まで待たずに別の作業をしつつ、気になったときに実行状況を確認できる方法について調べた。

そこで、今回がこのシリーズ最後のネタになる。

**そもそもGUIなんかいらんから、Pythonのスクリプトを実行して、その処理結果を受け取るようなメソッドとか作りたいんだけど、どうすんの？**

要するにGUIを介さず、Pythonスクリプトを開始し、実行が終わるまで次の処理に行かずにそこで待ち（同期をとる）、その後結果を取得するという形の呼び出しだ。

#ソース
はい、どん。

```PythonExecutor.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Text;

namespace ChemShow
{
    public class PythonExecutor
    {
        public String FileName { get; set; }
        public String WorkingDirectory { get; set; }
        public String Arguments { get; set; }
        public String InputString { get; set; }
        public String StandardOutput { get; set; }
        public int ExitCode { get; set; }

        private StringBuilder standardOutputStringBuilder = new StringBuilder();

        public PythonExecutor()
        {

        }

        /// 実行ボタンクリック時の動作
        public  void Execute()
        {
            
            ProcessStartInfo psInfo = new ProcessStartInfo();
            psInfo.FileName = this.FileName;
            psInfo.WorkingDirectory = this.WorkingDirectory;
            psInfo.Arguments = this.Arguments;

            psInfo.CreateNoWindow = true;
            psInfo.UseShellExecute = false;
            psInfo.RedirectStandardInput = true;
            psInfo.RedirectStandardOutput = true;
            psInfo.RedirectStandardError = true;

            // Process p = Process.Start(psInfo);
            Process p = new System.Diagnostics.Process();
            p.StartInfo = psInfo;
            p.OutputDataReceived += p_OutputDataReceived;
            p.ErrorDataReceived += p_ErrorDataReceived;

            // プロセスの実行
            p.Start();

            // 標準入力への書き込み
            using (StreamWriter sw = p.StandardInput)
            {
                sw.Write(InputString);
            }

            //非同期で出力とエラーの読み取りを開始
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();

            // 終わるまでまつ
            p.WaitForExit();
            this.ExitCode = p.ExitCode;
            this.StandardOutput = standardOutputStringBuilder.ToString();
        }

        /// <summary>
        /// 標準出力データを受け取った時の処理
        /// </summary>
        void p_OutputDataReceived(object sender,
            System.Diagnostics.DataReceivedEventArgs e)
        {
            //processMessage(sender, e);
            if (e != null && e.Data != null && e.Data.Length > 0)
            {
                standardOutputStringBuilder.Append(e.Data + "\n");
            }
        }

        /// <summary>
        /// 標準エラーを受け取った時の処理
        /// </summary>
        void p_ErrorDataReceived(object sender,
            System.Diagnostics.DataReceivedEventArgs e)
        {
            //必要な処理を書く
        }
    }
}
```

#ソース解説
目新しいことはなく、プロセスを開始したらそのまま```WaitForExit()```を実行しているだけ。そうするとプロセスが終了するまで次にいかない。これにより同期的な呼び出しが実現できる。実行終了後、終了コードや標準出力のデータをプロパティから取得できるようにしている。

#使い方
以下はスクリプトの標準入力にデータを与え、標準出力に出力された結果を受け取る例である。

```C#
PythonExecutor pe = new PythonExecutor();
pe.FileName=@"c:\python3.6\bin\python.exe";
pe.WorkingDirectory = @"c:\tmp";
pe.Arguments =@"C:\ChemShow\python\hist.py";
pe.InputString = "1,2,3,4,5,6,7,8,9,10"; //標準入力に与えるデータ
pe.Execute(); //実行
Console.WriteLine(pe.StandardOutput); //標準出力の結果を出力
```

#2020/2/24 修正
プロセスを2回スタートさせるという致命的バグがあったため以下の通り修正。ご迷惑おかけしました。

// Process p = Process.Start(psInfo);
Process p = new System.Diagnostics.Process();
p.StartInfo = psInfo;

#おわりに
要するに、今までに出てきた全ての呼び出し方に対応したコンポーネントをいい加減作れよという話。

後、タイトルはPythonスクリプトにしているが、コマンドラインから実行できるものであれば同じですね。