LoginSignup
5
4

More than 3 years have passed since last update.

Pythonnetを使って.NetからPythonコードを実行してみました(Hallo World編)

Last updated at Posted at 2020-07-31

こちらの記事で.NetからPythonを実行できると知り、Pythonnetを使ってみようと試してみました。
速攻でハマったので、備忘録としてメモしておきます。

環境

開発に用いている環境やPythonのバージョン、Pythonnetは以下の通りです。
- Windoiws 10 64bit
- Anaconda 3.8
- Visual Studio 2019
- Pythonnet 2020/7/31時点のソースコード

準備

まずは、Pythonnetからソースコード一式をダウンロードしてきます。
展開してpythonnet.slnがあるフォルダを開きます。

Pythonnetのコンパイル

ソリューションファイルpythonnet.slnを開きます。

対象とするOSやPythonバージョンなどは、プロジェクトの構成から
- ReleaseWinPy3(Windows版のPython3系リリース版)
- DebugMono(Mono版のPython2系デバッグ版)
などのような形で指定できるようになっています。

これをいじると、プロジェクトの「条件付きコンパイルシンボル」の定義値が書き換わって、ソース内の#ifによる条件が切り替わる仕組みですね。

runtime.csというソースコードを見てみると、以下のように切り替えられていることがわかります。

#if PYTHON34
        const string _minor = "4";
#elif PYTHON35
        const string _minor = "5";
#elif PYTHON36
        const string _minor = "6";
#elif PYTHON37
        const string _minor = "7";
#elif PYTHON38
        const string _minor = "8";
#else
#error You must define one of PYTHON34 to PYTHON38
#endif

#if WINDOWS
        internal const string dllBase = "python3" + _minor;
#else
        internal const string dllBase = "python3." + _minor;
#endif

これで、OSによるPythonDLLの名称の違いなどを切り替えているのですね。

今回の環境では、WindowsでPython3.8ですので、ReleaseWinPY3の構成でビルドします。
Python3.8を使うので、Python.Runtimeのプロパティの「ビルド」の項目の「条件付きコンパイルシンボル」の内容を、PYTHON3;PYTHON38;UCS2のように修正しておきましょう。

Python.RuntimeのプラットフォームはAny CPUしか選べないので、このまま。

ビルドすると、bin\Python.Runtime.dllができあがるので、これを好きな場所にコピーしておきます。

サンプルコード作成

さて、これでPythonnetライブラリの準備ができたので、簡単なHallo worldアプリを作ってみましょう。
こちらの記事にあるサンプルを参考に作ってみます。

なお、上記の記事ではC#で書いてありますが、こちらはVB.Netで書いてます。

プロジェクト作成

まず、Visual Studioで「新しいプロジェクトの作成」から、「コンソールアプリ(.NET Framework)」を選んでプロジェクトを作成します。
ここでは、フレームワークとして適当に.NET Framework 4.7.2を指定しました。

そして、Python.Runtime.dllを参照できるように、プロジェクトのプロパティから「参照設定」を選び、「追加」でPython.Runtime.dllを置いた場所を指定しましょう。

これで準備は完了です。

Hallo Worldなサンプルコード作成

あとは以下のサンプルコードを作成。

Imports Python.Runtime
Imports System
Imports System.IO
Imports System.Linq

Class TestPython
    Public Sub AddEnvPath(paths As String())
        Dim envPaths As IList(Of String) = Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator.ToString).ToList

        For Each path In paths
            If path.Length > 0 And Not envPaths.Contains(path) Then
                envPaths.Insert(0, path)
            End If
        Next
        Environment.SetEnvironmentVariable("PATH", String.Join(Path.PathSeparator.ToString, envPaths), EnvironmentVariableTarget.Process)
    End Sub
End Class

Module Module1

    Sub Main()
        Dim test_python = New TestPython

        Dim PYTHON_HOME = "C:\Users\xxx\anaconda3\envs\Python38"

        vms_python.AddEnvPath({PYTHON_HOME,
                               Path.Combine(PYTHON_HOME, "Library\bin")})

        PythonEngine.PythonHome = PYTHON_HOME

        Dim python_paths() As String = {PythonEngine.PythonPath, Path.Combine(PYTHON_HOME, "Lib\site-packages"), Path.Combine("C:\tmp")}
        PythonEngine.PythonPath = String.Join(Path.PathSeparator.ToString, python_paths)

        PythonEngine.Initialize()

        Using Py.GIL()
            PythonEngine.RunSimpleString("import sys")
            PythonEngine.RunSimpleString("print(""Hallo World"")")
        End Using

    End Sub

End Module

内容としては、
- 環境変数PATHにパスを追加するヘルパークラスTestPythonを作成
- PYTHON_HOMEにAnacondaのパスを記述
- PYTHON_HOMEPYTHON_HOME\Library\binを環境変数PATHに追加
という準備をおこなってから、Python.Runtimeに対して、
- PythonEngine.PythonHome = PYTHON_HOMEでPythonがある場所を教える
- PythonEngine.PythonPathにPythonのサーチパスを教える
- PythonEngine.Initialize()で初期化
としたのち、

Using Py.GIL()
    PythonEngine.RunSimpleString("import sys")
    PythonEngine.RunSimpleString("print(""Hallo World"")")
End Using

Hallo Worldを表示するという単純なプログラムです。

実行

というわけで実行してみましょう。

ハンドルされていない例外: System.DllNotFoundException: DLL 'python3.8' を読み込めません:指定されたモジュールが見つかりません。 (HRESULT からの例外:0x8007007E)

DLLが読み込めないというエラーです。

原因と解決方法

とりあえず、原因と解決方法だけ、さくっと書きます。

調べてみたところ、

  • python38.dllではなく、python3.8.dllを探しに行っている(Windowsではpython38.dllという名前)
  • dumpbinコマンドで調べてみると、Python.Runtime.dllがx86としてビルドされている

の二点が原因だとわかりました。
(2020/08/04 コメントいただき修正。Python.Runtime.dllがAnyCPUでビルドされているのは問題ありませんでした。呼び出し側のプログラムをx64でビルドしていたつもりが、AnyCPUになっていたため、32bit呼び出しになっていたようです。呼び出し側プログラムのAnyCPUは念の為削除しておいた方が安全なようです)

よって、解決方法は以下の二点です。

  • Pythonnetのビルド時にWINDOWSのシンボルが定義されていないためpython3.8.dllという名前のDLLを探しに行っているので、Python.Runtimeの「条件付きコンパイルシンボル」にWINDOWSシンボルを追加する
  • Python.Runtimeのターゲットプラットフォームをx64にする

こうしてビルドしたDLLを参照するようにする(一度古いDLLを除外してから再度参照させる必要あり)と、以下のように無事に実行結果が得られました。

Hallo World

なんか、Hallo Worldだけでかなり苦労したので、この記事はここまで。
実際にPythonを呼び出して使っていくコードを書くのは、次の記事にしたいと思います。

5
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4