#はじめに
C# GUIアプリケーションからPythonスクリプトを実行する の延長で、GUIから直接入力したpythonのソースコードを実行できるようにしたという話。
#作ったもの
画面は以下の通りだ。画面ではnumpyにより計算を実行した結果を表示している。
#ソース
ユーザが入力した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;