こちらの記事で.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_HOME
とPYTHON_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を呼び出して使っていくコードを書くのは、次の記事にしたいと思います。