【C# .NET8.0】C#からPythonを実行し、結果を取得【Python】 (original) (raw)
画像処理でどうしてもPythonでしか実装出来ないプログラムがあり
C#からPythonを呼び出しかなかった為、その内容をまとめます。
Pythonの呼び出し方法
この他にもいくつか方法はありますが、一番楽な方法としては上記かと思います。
1・3は配布先にPython環境が必要な為、自分的には候補から外れ、必然的に2となります。
2は簡単に実装出来ますが、デメリットとしては
Pythonへの引数と戻り値
C# から Python への値の受け渡しは、起動時に渡せる コマンドライン引数 と input() 関数が使用できます。
C# → Python は上記の様に簡単に値を渡せますが
Python → C# は 共有メモリ や ファイル の読み書きでの値渡しが必要になります。
共有メモリ で行うのが一番いいですが、バイト数 を揃えたりと処理が大変な為、今回はファイルの読み書きで戻り値を取得します。
サンプルコード (Python)
import sys
def textWriter(text): f = open('result', 'w') f.write(text) f.close()
if name == 'main': try:
args = sys.argv
if len(args) > 1 and args[1] == 'debug': debug = 1
else: debug = 0
textWriter('Ready')
while True:
inputText = input()
textWriter('')
if inputText == 'exit': break
textWriter('Complete')
if debug == 1:
pass
while True:
if input() == 'Receive':
textWriter('Ready')
break;
finally:
textWriter('')
Pythonのexe化
コマンドプロンプト を開き、Pythonファイルのあるフォルダへ移動し
pyinstaller ファイル名.py --onefile
を実行します。
成功すると、『dist』フォルダ内にexeファイルが生成されます。
生成されたexeファイルはC#の実行ファイルと同じフォルダに配置してください。
サンプルコード (C#)
using System.Diagnostics;
namespace pyApiTest1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); }
private Process? Process;
private StreamWriter? sw;
private bool flgRady = false;
private void Form1_Load(object sender, EventArgs e)
{
Process = new Process();
Process.StartInfo.FileName = "dist/objectDetection.exe";
Process.StartInfo.Arguments = "debug";
Process.StartInfo.UseShellExecute = false;
Process.StartInfo.RedirectStandardInput = true;
Process.StartInfo.RedirectStandardOutput = true;
Process.StartInfo.CreateNoWindow = true;
Process.EnableRaisingEvents = true;
Process.Exited += OnExited;
Process.Start();
sw = Process.StandardInput;
if (PyTextReader(30) == "Ready")
flgRady = true;
else
this.Close();
}
private void OnExited(object? sender, EventArgs e)
{
Process?.WaitForExit();
Process?.Close();
this.Dispose();
}
private string PyTextReader(int timeout)
{
string str = "";
for (int i = 0; i <= timeout * 10; i++)
{
System.Threading.Thread.Sleep(100);
if (File.Exists("result") == false)
continue;
try
{
using (StreamReader? sr = new StreamReader("result"))
{
str = sr.ReadLine()??"";
if (str != "")
break;
}
}
catch
{
}
}
return str;
}
private void Form1_Closed(object sender, EventArgs e)
{
if (sw != null)
{
sw.WriteLine("exit");
sw.Close();
}
Process?.WaitForExit();
Process?.Close();
this.Dispose();
}
private void PyTexWriter(string str)
{
if (flgRady == false) return;
sw?.WriteLine(str);
var result = PyTextReader(5);
sw?.WriteLine("Receive");
}
}
}
解説 (Python)
def textWriter(text): f = open('result', 'w') f.write(text) f.close()
resultファイルを開き、text を書き込む関数です。
resultファイルはC#への戻り値に使用しています。
ファイルがない場合は自動的に生成されます。
close しないとC#側から読み込めない為、書き込んだ後は close してください。
args = sys.argv
if len(args) > 1 and args[1] == 'debug': debug = 1
else: debug = 0
textWriter('Ready')
コマンドライン引数の処理を行っております。
今回は debug の引数を取り込み、debug 時の処理を作成出来るようにしています。
初期化処理の完了として、最後に戻り値 Ready を書き込んでいます。
while True:
inputText = input()
textWriter('')
if inputText == 'exit': break
textWriter('Complete')
if debug == 1:
pass
C#からの引数入力を待機し、入力完了後にメイン処理を実行します。
textWriter('') で戻り値を初期化しています。
exit の入力でプログラムを終了します。
while True:
if input() == 'Receive':
textWriter('Ready')
break;
textWriter('Complete') でC#への処理完了を送信し
C#から Receive の入力が入ってくるのを待ちます。
finally:
textWriter('')
プロセス終了時にresultファイルを空にします。
解説 (C#)
Process = new Process();
Process.StartInfo.FileName = "dist/objectDetection.exe";
Process.StartInfo.Arguments = "debug";
Process.StartInfo.UseShellExecute = false;
Process.StartInfo.RedirectStandardInput = true;
Process.StartInfo.RedirectStandardOutput = true;
Process.StartInfo.CreateNoWindow = true;
Process.EnableRaisingEvents = true;
Process.Exited += OnExited;
Process.Start();
sw = Process.StandardInput;
プロパティは全て必須です。
各プロパティの意味はこちらをご確認ください。
Process.Start() でPythonを起動しますが、起動に7秒ほどかかる為、起動中ダイアログ等を表示すると良いです。
Python起動後に sw = Process.StandardInput でStreamWriterを取得します。
ただし、この sw は一度閉じると再取得できない為、グローバル変数に入れています。
if (PyTextReader(30) == "Ready")
flgRady = true;
else
this.Close();
resultファイルに Ready が書き込まれるのを待ちます。※Python側準備完了待ち
PyTextReader メソッドの解説は後述です。
private void OnExited(object? sender, EventArgs e)
{
Process?.WaitForExit();
Process?.Close();
this.Dispose();
}
Pythonアプリが終了した際の処理です。
Python側のエラー等で終了した場合も発火するはずです。
private void Form1_Closed(object sender, EventArgs e)
{
if (sw != null)
{
sw.WriteLine("exit");
sw.Close();
}
Process?.WaitForExit();
Process?.Close();
this.Dispose();
}
C#側のフォームクローズでPythonへのプロセス終了を送信しています。
private string PyTextReader(int timeout)
{
string str = "";
for (int i = 0; i <= timeout * 10; i++)
{
System.Threading.Thread.Sleep(100);
if (File.Exists("result") == false)
continue;
try
{
using (StreamReader? sr = new StreamReader("result"))
{
str = sr.ReadLine()??"";
if (str != "")
break;
}
}
catch
{
}
}
return str;
}
resultファイルに文字列が格納されるまでひたすら待ち、入力があれば結果を返す関数です。
timeout は100ms単位で設定します。
private void PyTexWriter(string str)
{
if (flgRady == false) return;
sw?.WriteLine(str);
var result = PyTextReader(5);
sw?.WriteLine("Receive");
}
Pythonへの出力関数です。
swは1度閉じてしまうと再取得が出来なくなるので、アプリ開始から終了まで同じ物を使用します。
あとがき
無理やり感はありますが、なんとかC#とPythonでAPI連携する事が出来ました。
一番いい方法は、配布側にもPython環境を入れて、PythonをDLLとして読み込む
もしくは、C++で書き直しDLLで読むのが一番処理速度が速いと思います。