画像処理でどうしてもPythonでしか実装出来ないプログラムがあり
C#からPythonを呼び出しかなかった為、その内容をまとめます。
Pythonの呼び出し方法
この他にもいくつか方法はありますが、一番楽な方法としては上記かと思います。
1・3は配布先にPython環境が必要な為、自分的には候補から外れ、必然的に2となります。
2は簡単に実装出来ますが、デメリットとしては
Pythonへの引数と戻り値
C#から Pythonへの値の受け渡しは、起動時に渡せる コマンドライン引数と input()関数が使用できます。
C#→ Pythonは上記の様に簡単に値を渡せますが
Python→ C#は 共有メモリや ファイルの読み書きでの値渡しが必要になります。
共有メモリ で行うのが一番いいですが、バイト数 を揃えたりと処理が大変な為、今回はファイルの読み書きで戻り値を取得します。
サンプルコード (Python)
# coding: utf-8import sys deftextWriter(text): f = open('result', 'w') f.write(text) f.close() if __name__ == '__main__': try: # Initialization args = sys.argv iflen(args) > 1and args[1] == 'debug': debug = 1else: debug = 0 textWriter('Ready') whileTrue: # Input inputText = input() textWriter('') if inputText == 'exit': break# Main Process textWriter('Complete')
# Debug Processif debug == 1: pass# ReceivewhileTrue: ifinput() == 'Receive': textWriter('Ready') break; finally: textWriter('')
Pythonのexe化
コマンドプロンプトを開き、Pythonファイルのあるフォルダへ移動し
pyinstaller ファイル名.py --onefile
を実行します。
成功すると、『dist』フォルダ内にexeファイルが生成されます。
生成されたexeファイルはC#の実行ファイルと同じフォルダに配置してください。
サンプルコード (C#)
using System.Diagnostics; namespace pyApiTest1 { publicpartialclassForm1 : Form { public Form1() { InitializeComponent(); } private Process? Process; private StreamWriter? sw; privatebool flgRady =false; privatevoid 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; elsethis.Close(); } privatevoid OnExited(object? sender, EventArgs e) { Process?.WaitForExit(); Process?.Close(); this.Dispose(); } privatestring 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; } privatevoid Form1_Closed(object sender, EventArgs e) { if (sw !=null) { sw.WriteLine("exit"); sw.Close(); } Process?.WaitForExit(); Process?.Close(); this.Dispose(); } privatevoid PyTexWriter(string str) { if (flgRady ==false) return; sw?.WriteLine(str); var result = PyTextReader(5); sw?.WriteLine("Receive"); } } }
解説 (Python)
deftextWriter(text): f = open('result', 'w') f.write(text) f.close()
resultファイルを開き、textを書き込む関数です。
resultファイルはC#への戻り値に使用しています。
ファイルがない場合は自動的に生成されます。
closeしないとC#側から読み込めない為、書き込んだ後は closeしてください。
# Initialization args = sys.argv iflen(args) > 1and args[1] == 'debug': debug = 1else: debug = 0 textWriter('Ready')
コマンドライン引数の処理を行っております。
今回は debugの引数を取り込み、debug時の処理を作成出来るようにしています。
初期化処理の完了として、最後に戻り値 Ready を書き込んでいます。
whileTrue: # Input inputText = input() textWriter('') if inputText == 'exit': break# Main Process
textWriter('Complete')
# Debug Processif debug == 1: pass
C#からの引数入力を待機し、入力完了後にメイン処理を実行します。
textWriter('')で戻り値を初期化しています。
exitの入力でプログラムを終了します。
# ReceivewhileTrue: ifinput() == '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; elsethis.Close();
resultファイルに Readyが書き込まれるのを待ちます。※Python側準備完了待ち
PyTextReaderメソッドの解説は後述です。
privatevoid OnExited(object? sender, EventArgs e) { Process?.WaitForExit(); Process?.Close(); this.Dispose(); }
Pythonアプリが終了した際の処理です。
Python側のエラー等で終了した場合も発火するはずです。
privatevoid Form1_Closed(object sender, EventArgs e) { if (sw !=null) { sw.WriteLine("exit"); sw.Close(); } Process?.WaitForExit(); Process?.Close(); this.Dispose(); }
C#側のフォームクローズでPythonへのプロセス終了を送信しています。
privatestring 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単位で設定します。
privatevoid 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で読むのが一番処理速度が速いと思います。