Quantcast
Channel: 製造業 スマートDX推進課 スマート工場DIY日記
Viewing all articles
Browse latest Browse all 5

【C# .NET8.0】C#からPythonを実行し、結果を取得【Python】

$
0
0

画像処理でどうしてもPythonでしか実装出来ないプログラムがあり
C#からPythonを呼び出しかなかった為、その内容をまとめます。

Pythonの呼び出し方法

C#からPythonを呼び出す方法はいくつかあります。

  1. プロセスから Pythonファイルを実行
  2. プロセスから Pythonをexe化したファイルを実行
  3. Python.NET ライブラリを使用

この他にもいくつか方法はありますが、一番楽な方法としては上記かと思います。
1・3は配布先にPython環境が必要な為、自分的には候補から外れ、必然的に2となります。

2は簡単に実装出来ますが、デメリットとしては

  • Pythonから出力されるexeファイルの容量が大きい (200MB以上)
  • Pythonの起動に時間が掛かる (7秒程度)

Pythonへの引数と戻り値

C#から Pythonへの値の受け渡しは、起動時に渡せる コマンドライン引数input()関数が使用できます。

C#Pythonは上記の様に簡単に値を渡せますが
PythonC#共有メモリファイルの読み書きでの値渡しが必要になります。

共有メモリ で行うのが一番いいですが、バイト数 を揃えたりと処理が大変な為、今回はファイルの読み書きで戻り値を取得します。

サンプルコード (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の入力が入ってくるのを待ちます。

再度、C#Ready送信しC#からの入力を待ちます。

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;

C#側から Pythonプログラムを呼び出します。

プロパティは全て必須です。
各プロパティの意味はこちらをご確認ください。

learn.microsoft.com

 

Process.Start()Pythonを起動しますが、起動に7秒ほどかかる為、起動中ダイアログ等を表示すると良いです。

Python起動後に sw = Process.StandardInputStreamWriterを取得します。
ただし、この 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#PythonAPI連携する事が出来ました。

一番いい方法は、配布側にもPython環境を入れて、PythonをDLLとして読み込む
もしくは、C++で書き直しDLLで読むのが一番処理速度が速いと思います。


Viewing all articles
Browse latest Browse all 5

Trending Articles