LoginSignup
1
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-01-25

はじめに

C# GUIアプリケーションからPythonスクリプトを実行する の延長で、GUIから直接入力したpythonのソースコードを実行できるようにしたという話。

作ったもの

画面は以下の通りだ。画面ではnumpyにより計算を実行した結果を表示している。
image.png

また文法が違っている場合は以下のようにエラーが表示される。
image.png

ソース

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

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

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

最後にソースを記載しておく。解説はC# GUIアプリケーションからPythonスクリプトを実行する を参考にしてほしい。

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;

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5