Python has many great libraries, including machine learning. On the other hand, C # is a language widely used for developing GUI applications. Therefore, it would be convenient for C # application developers to be able to call Python scripts from C # applications, and above all, the range of GUI applications should be expanded. So, this time, I investigated how to call a Python script from a C # GUI application and created a prototype.
I have identified the requirements of the prototype I want to develop below.
--You can specify and execute Python paths, Working Directory, and Python scripts. ――In consideration of the case where it takes a long time to execute, you can cancel the process in the middle. --Display all standard output and standard error output in the GUI TextBox so that you can see the progress if it takes a long time to execute. --For a Python script that accepts standard input, such as Convert MOL file to SMILES via standard I / O, the file By passing the data on the GUI side to the Python script without going through it, you can easily receive the execution result. Therefore, I want to be able to specify standard input as well. --The exit code of the Python script determines whether it ended normally or with an error, and it is displayed by MessageBox.
Like this.
--For Python Path, specify the location of python.exe with the full path. For Anaconda, check the location of python.exe in Anaconda's virtual environment and specify it. --Specify the execution directory of the Python script in Working Directory. When a file path is specified as an argument, it can be described as a relative path starting from here. --In Python Command, specify the location of the Python script with the full path. If there is an argument, specify it as well. --In Standard Input, enter the data you want to pass to the standard input of the Python script. Ignored for Python scripts that do not use standard input. --Standard Output and Standard Error displays all standard output and standard error output of Python scripts. --You can start the process with the "Execute" button and cancel the process with the "Cancel" button.
The source is as follows. It's been very long, but it's awkward to edit, so I just paste it (cut out). The code on the design side is omitted. Please read the variable name of the object of the GUI part from the code. The source will be explained in the next section.
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace PythonCommandExecutor
{
public partial class Form1 : Form
{
private Process currentProcess;
private StringBuilder outStringBuilder = new StringBuilder();
private int readCount = 0;
private Boolean isCanceled = false;
public Form1()
{
InitializeComponent();
}
/// <summary>
///Add string to Textbox
/// </summary>
public void AppendText(String data, Boolean console)
{
textBox1.AppendText(data);
if (console)
{
textBox1.AppendText("\r\n");
Console.WriteLine(data);
}
}
/// <summary>
///Behavior when the execute button is clicked
/// </summary>
private void button1_Click(object sender, EventArgs e)
{
//Preprocessing
button1.Enabled = false;
button2.Enabled = true;
isCanceled = false;
readCount = 0;
outStringBuilder.Clear();
this.Invoke((MethodInvoker)(() => this.textBox1.Clear()));
//Run
RunCommandLineAsync();
}
/// <summary>
///Command execution process body
/// </summary>
public void RunCommandLineAsync()
{
ProcessStartInfo psInfo = new ProcessStartInfo();
psInfo.FileName = this.textBox2.Text.Trim();
psInfo.WorkingDirectory = this.textBox3.Text.Trim();
psInfo.Arguments = this.textBox4.Text.Trim();
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();
//Write to standard input
using (StreamWriter sw = p.StandardInput)
{
sw.Write(this.textBox5.Text.Trim());
}
//Start reading output and error asynchronously
p.BeginOutputReadLine();
p.BeginErrorReadLine();
currentProcess = p;
}
void onExited(object sender, EventArgs e)
{
int exitCode;
if (currentProcess != null)
{
currentProcess.WaitForExit();
//Exhaling data that remains without being exhaled
this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false)));
outStringBuilder.Clear();
exitCode = currentProcess.ExitCode;
currentProcess.CancelOutputRead();
currentProcess.CancelErrorRead();
currentProcess.Close();
currentProcess.Dispose();
currentProcess = null;
this.Invoke((MethodInvoker)(() => this.button1.Enabled = true));
this.Invoke((MethodInvoker)(() => this.button2.Enabled=false));
if (isCanceled)
{
//Completion message
this.Invoke((MethodInvoker)(() => MessageBox.Show("Canceled processing")));
}
else
{
if (exitCode == 0)
{
//Completion message
this.Invoke((MethodInvoker)(() => MessageBox.Show("Processing is complete")));
}
else
{
//Completion message
this.Invoke((MethodInvoker)(() => MessageBox.Show("An error has occurred")));
}
}
}
}
/// <summary>
///Processing when standard output data is received
/// </summary>
void p_OutputDataReceived(object sender,
System.Diagnostics.DataReceivedEventArgs e)
{
processMessage(sender, e);
}
/// <summary>
///What to do when a standard error is received
/// </summary>
void p_ErrorDataReceived(object sender,
System.Diagnostics.DataReceivedEventArgs e)
{
processMessage(sender, e);
}
/// <summary>
///Receives CommandLine program data and spits it into a TextBox
/// </summary>
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++;
//Exhale at a cohesive timing
if (readCount % 5 == 0)
{
this.Invoke((MethodInvoker)(() => AppendText(outStringBuilder.ToString(), false)));
outStringBuilder.Clear();
//Put sleep to not occupy threads
if (readCount % 1000 == 0)
{
Thread.Sleep(100);
}
}
}
/// <summary>
///Behavior when the cancel button is clicked
/// </summary>
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)
{
//Clear standard input area
this.textBox5.Clear();
//Clear standard output area
this.textBox1.Clear();
}
}
}
It's basically a collection of references, but the explanation is given below.
--The Python script is being executed by the Process class in the RunCommandLineAsync method. Since the processing becomes asynchronous after `p.Start ()`
, if you operate the UI after that, you will get angry if you do not execute it from the UI thread. `this.Invoke ((MethodInvoker) (() => AppendText (outStringBuilder.ToString (), false)));
This is why there are some calls like. --``` p.EnableRaisingEvents = true;
,
p.Exited + = onExited;
executes the onExit event handler at the end of the process, so cleanup processing and cleanup processing here It describes the judgment of the end code, the display of the completion dialog, etc. --For cancellation, the Kill method of Process class is called in the event handler executed at the time of cancellation. Then the onExit event handler is executed, which is the same as when it normally ends, so nothing else is done. --The place where the standard input feeds the data is done from the beginning of ```using (StreamWriter sw = p.StandardInput)
. --For standard output and standard error output, ``
p.OutputDataReceived + = p_OutputDataReceived;
,
ErrorDataReceived + = p_ErrorDataReceived;` `` to receive standard output and standard error in each event handler. I am trying to process the output.
p.BeginOutputReadLine();,p.BeginErrorReadLine();Since each event handler is executed every time there is one line output, the output to TextBox is performed in it. If you write each line to a TextBox, it may take a long time to process the GUI in the case of an application that has a large amount of output, so we are trying to output it all together to some extent.
# Execution example (1) Execute a Python script with a lot of output (an error in the middle)
The following is an example of executing a Python script created as a sample, which has a certain amount of standard output and standard error output. It can be seen that the error message output to the standard error is also output to the TextBox, and the error MessageBox is displayed by the exit code.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/f2c9e895-314f-f7e9-795a-2588ca113c6d.png)
# Execution example (2) Execute Python script via standard input
The following is a diagram of executing the script of "[Convert MOL file to SMILES via standard input / output](https://qiita.com/kimisyo/items/c7eb3a6a10298590438e)" through this GUI. You will realize that you can pass the processing results of C # and Python just by writing a simple Python script.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329816/f46c8aee-2950-836c-7a00-5d10fad6021d.png)
# in conclusion
--I was initially proceeding with the method using async / await, but I gave up because a phenomenon like a deadlock in the UI occurred and I could not solve it even after a whole day.
--By outputting progress information to the standard output of Python script, I think you can easily display the progress with the progress bar on the C # side.
――Since the operation is fairly stable, I would like to make an attractive application by using the convenient functions of Python from C # based on this prototype.
# 2020/2/24 revised
Fixed as follows because there was a fatal bug that started the process twice. We apologize for the inconvenience.
// Process p = Process.Start(psInfo);
Process p = new System.Diagnostics.Process();
p.StartInfo = psInfo;
# References
-[Process class](https://docs.microsoft.com/ja-jp/dotnet/api/system.diagnostics.process?view=netframework-4.8)
-[Notes on receiving standard output by starting asynchronous external process](https://qiita.com/skitoy4321/items/10c47eea93e5c6145d48)
--[Start an external application and wait for it to finish](https://dobon.net/vb/dotnet/process/openfile.html)
-[What if the process freezes when the external program is executed? ](Https://www.atmarkit.co.jp/fdotnet/dotnettips/805pipeasync/pipeasync.html)
Recommended Posts