C# GUIからPythonのコードを実行する

#はじめに
[C# GUIアプリケーションからPythonスクリプトを実行する](https://qiita.com/kimisyo/items/0879fc9f1315e2abfd1f) の延長で、GUIから直接入力したpythonのソースコードを実行できるようにしたという話。

#作ったもの

画面は以下の通りだ。画面ではnumpyにより計算を実行した結果を表示している。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/f99162e7-203a-e788-6cf9-e558d78bfd57.png)

また文法が違っている場合は以下のようにエラーが表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/ab7a6c51-01fb-8257-3053-5b6464a1588f.png)

#ソース
ユーザが入力したPythonのコードをpythonスクリプトとして一時フォルダに保存し、それをpytnon.exeに実行させるようにしただけで、技術的に目新しいことは全くない。

ただ、その場で自由にpythonのコードを入力し、実行できるのは楽しいものだ。

ふと浮かんだ応用例として、どうしても見られたくないPythonコードを配布する場合に使えるのではないか。

最後にソースを記載しておく。解説は[C# GUIアプリケーションからPythonスクリプトを実行する](https://qiita.com/kimisyo/items/0879fc9f1315e2abfd1f) を参考にしてほしい。

```C#
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace PythonExecutor
{

    public partial class Form1 : Form
    {
        private Process currentProcess;
        private StringBuilder outStringBuilder = new StringBuilder();
        private int readCount = 0;
        private Boolean isCanceled = false;
        private String pythonFileName = "temporaryPythonFile.py";
        
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Textboxに文字列追加
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void AppendText(String data, Boolean console)
        {
            textBox1.AppendText(data);
            if (console)
            {
                textBox1.AppendText("\r\n");
                Console.WriteLine(data);
            }
        }

        /// <summary>
        /// 実行ボタンクリック時の動作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {

            if (Directory.Exists(this.textBox3.Text.Trim()))
            {
                // workフォルダにpythonファイルに書き込む
                String path = this.textBox3.Text.Trim() + System.IO.Path.DirectorySeparatorChar + this.pythonFileName;

                using (StreamWriter writer = new StreamWriter(path))
                {
                    StringReader strReader = new StringReader(this.textBox4.Text.Trim());
                    while (true)
                    {
                        String aLine = strReader.ReadLine();
                        if (aLine != null)
                        {
                            writer.WriteLine(aLine);

                        }
                        else
                        {
                            break;
                        }
                    }
                    writer.Close();
                }

                // 前処理
                button1.Enabled = false;
                button2.Enabled = true;
                isCanceled = false;
                readCount = 0;
                outStringBuilder.Clear();
                this.Invoke((MethodInvoker)(() => this.textBox1.Clear()));

                // 実行
                RunCommandLineAsync(path);
            }
            else
            {
                this.Invoke((MethodInvoker)(() => MessageBox.Show("Workingフォルダが無効です")));
            }

        }

        /// <summary>
        /// コマンド実行処理本体
        /// </summary>
        public void RunCommandLineAsync(String pythonScriptPath)
        {

            ProcessStartInfo psInfo = new ProcessStartInfo();
            psInfo.FileName = this.textBox2.Text.Trim();
            psInfo.WorkingDirectory = this.textBox3.Text.Trim();
            // psInfo.Arguments = this.textBox4.Text.Trim();
            psInfo.Arguments = pythonScriptPath;

            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.EnableRaisingEvents = true;
            p.Exited += onExited;
            p.OutputDataReceived += p_OutputDataReceived;
            p.ErrorDataReceived += p_ErrorDataReceived;

            p.Start();


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

            currentProcess = p;
        }

        void onExited(object sender, EventArgs e)
        {
            int exitCode;

            if (currentProcess != null)
            {
                currentProcess.WaitForExit();

                // 吐き出されずに残っているデータの吐き出し
                this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false)));
                outStringBuilder.Clear();

                exitCode = currentProcess.ExitCode;
                currentProcess.CancelOutputRead();
                currentProcess.CancelErrorRead();
                currentProcess.Close();
                currentProcess.Dispose();
                currentProcess = null;

                // pythonファイルを削除する
                String pythonFilepath = this.textBox3.Text.Trim() + System.IO.Path.DirectorySeparatorChar + this.pythonFileName;
                if (File.Exists(pythonFilepath)) ;
                {
                    File.Delete(pythonFilepath);
                }

                this.Invoke((MethodInvoker)(() => this.button1.Enabled = true));
                this.Invoke((MethodInvoker)(() => this.button2.Enabled=false));

                if (isCanceled)
                {
                    // 完了メッセージ
                    this.Invoke((MethodInvoker)(() => MessageBox.Show("処理をキャンセルしました")));
                }
                else
                {
                    if (exitCode == 0)
                    {
                        // 完了メッセージ
                        this.Invoke((MethodInvoker)(() => MessageBox.Show("処理が完了しました")));
                    }
                    else
                    {
                        // 完了メッセージ
                        this.Invoke((MethodInvoker)(() => MessageBox.Show("エラーが発生しました")));
                    }
                }
            }
        }

        /// <summary>
        /// 標準出力データを受け取った時の処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void p_OutputDataReceived(object sender,
            System.Diagnostics.DataReceivedEventArgs e)
        {
            processMessage(sender, e);
        }

        /// <summary>
        /// 標準エラーを受け取った時の処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void p_ErrorDataReceived(object sender,
            System.Diagnostics.DataReceivedEventArgs e)
        {
            processMessage(sender, e);
        }

        /// <summary>
        /// CommandLineプログラムのデータを受け取りTextBoxに吐き出す
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void processMessage(object sender, System.Diagnostics.DataReceivedEventArgs e)
        {
            if (e != null && e.Data != null && e.Data.Length > 0)
            {
                outStringBuilder.Append(e.Data + "\r\n");
            }
            readCount++;
            // まとまったタイミングで吐き出し
            if (readCount % 5 == 0)
            {
                this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false)));
                outStringBuilder.Clear();
                // スレッドを占有しないようスリープを入れる
                if (readCount % 1000 == 0)
                {
                    Thread.Sleep(100);
                }
            }
        }

        /// <summary>
        /// キャンセルボタンクリック時の動作
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            if (currentProcess != null)
            {
                try
                {
                    currentProcess.Kill();
                    isCanceled = true;
                }
                catch (Exception e2)
                {
                    Console.WriteLine(e2);
                }
            }
        }

        private void button3_Click(object sender, EventArgs e)
        {
            // Pythonコード部分のクリア
            this.textBox4.Clear();
            // 標準出力エリアのクリア
            this.textBox1.Clear();
        }
        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }
    }
}
```

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

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