これの続き。
前回の「ほぼ結論」:
で、残るは
Python.Runtime.dll
なのだが…、やはりアンマネージドな DLL とは違うのね、アッセンブリを見つける仕組みは。「GAC に置かず、なおかつ自分と同じ場所に置かない」という場合のアッセンブリの解決方法が見つからない。自分の EXE や DLL と同じ名前の設定ファイル(今の場合「PyFromCSExam.exe.config
」)を書くと一定の制御が出来ることは発見したものの、例えば <probing> は目的のものに近いのに、「あんたの EXE や DLL のサブフォルダなら許してやるぜっ」てものなので、今やりたい「全然無関係の場所にある2つ」を結びつける手段にはならない。
GAC にないものだからねぇ。「コピーすべし」が唯一の解なのかもしらんなぁ。(も少し粘って調べてみるけれど、たぶんこれが答えなんじゃないかと今のところ思ってる。)
予想通りほとんど正しい。なぜなら「確実に想定した組み合わせで動いて欲しい」と願うべきだからだ、普通は。「DLL Hell」。
とはいえ、「サードパーティライブラリのバージョンアップで何か致命的バグが直ったら追従したい」といったニーズも普通は考えるし、ワタシのように、「絶賛開発中とか自分しか使わないものでまで厳格に規則に従うなんてヤダヤダヤダ」のためにも、地獄への落ち方を知っておくことは必要だ。
You can invoke DLL Hell by writing an event handler for the AppDomain.CurrentDomain.AssemblyResolve event. Assign it in the Main() method.
それな。
書き方は CodeProject からすぐに見つかった。
つーわけで概ねこんな具合な:
1 # -*- coding: utf-8 -*-
2 # ---------------------------------------------------
3 from __future__ import absolute_import
4 from __future__ import unicode_literals
5 from __future__ import print_function
6
7
8 if __name__ == '__main__':
9 # ファイルがバラけてると実験が鬱陶しいので C# コードをここに書いてしまって
10 # ビルドも全部やってしまう。
11 import subprocess, os, io, sys
12
13
14 # site-packages にある Python.Runtime.dll
15 import site
16 cands = site.getsitepackages()
17 for canddir in cands[1:]:
18 pythonengine_dll = os.path.join(canddir, "Python.Runtime.dll")
19 if os.path.exists(pythonengine_dll):
20 break
21
22 # C# コード
23 # 公式サイト説明に欠けてるのは:
24 # using Python.Runtime;
25 # PythonEngine.Initialize(); がいるのかいらないのか、入ってなくても
26 # 動いた。CPython の C API のノリなら必要なのだが?
27 #
28 io.open("PyFromCSExam.cs", "w").write("""
29 using System; // for Console, etc.
30 using System.Collections.Generic; // for List
31 using System.Reflection; // for Assembly
32
33 using Python.Runtime;
34
35 namespace PyFromCSExam
36 {
37 public class Exam
38 {
39 static Exam()
40 {
41 AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
42 }
43 static void Main(string[] args)
44 {
45 using (Py.GIL())
46 {
47 dynamic np = Py.Import("numpy");
48
49 Console.WriteLine(np.cos(np.pi * 2));
50
51 dynamic sin = np.sin;
52 Console.WriteLine(sin(5));
53
54 double c = np.cos(5) + sin(5);
55 Console.WriteLine(c);
56 }
57 }
58 static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
59 {
60 return Assembly.LoadFile("%s");
61 }
62 }
63 }
64 """ % (pythonengine_dll.replace(os.path.sep, "/")))
65
66 # ビルド
67
68 # pythonXX.dll の場所を PATH に追加しちまえよ
69 from distutils import sysconfig
70 pydlldir = sysconfig.get_config_var("BINDIR") # unix系では LIBDIR のヤツ。
71 if sys.version_info[0] == 2:
72 def _fixenv(s):
73 return s.encode()
74 else:
75 def _fixenv(s):
76 return s
77 os.environ["PATH"] = _fixenv(";".join([
78 pydlldir]) + ";" + os.environ.get("PATH", ""))
79
80 # ビルドしちゃいなよ
81 subprocess.check_call(
82 [
83 # csc がない、と言われる場合で「ほんとうは持ってる」場合は、
84 # BuildTools 2007 なら、スタートメニューから「x64_x86 Cross Tools
85 # Command Prompt for VS 2017」(等)を探して起動し、その環境から。
86 "csc.exe",
87 "-nologo",
88 "-target:exe", # as console app
89 "-out:PyFromCSExam.exe",
90 #"-reference:" + os.path.basename(pythonengine_dll),
91 "-reference:" + pythonengine_dll,
92 "PyFromCSExam.cs",
93 ]
94 )
95 # 実行しちゃいなよ
96 subprocess.check_call(
97 [
98 "PyFromCSExam.exe",
99 ],
100 env=os.environ)
今回のは Python.Runtime.dll
をカレントにコピーしないので、ビルド時の「-reference:
」に Python.Runtime.dll
へのフルパスを渡している。
ちと C# 部分が読みにくいかもしらんので、抽出するとこうしてる:
1 using System; // for Console, etc.
2 using System.Collections.Generic; // for List
3 using System.Reflection; // for Assembly
4
5 using Python.Runtime;
6
7 namespace PyFromCSExam
8 {
9 public class Exam
10 {
11 static Exam()
12 {
13 AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
14 }
15 static void Main(string[] args)
16 {
17 using (Py.GIL())
18 {
19 dynamic np = Py.Import("numpy");
20
21 Console.WriteLine(np.cos(np.pi * 2));
22
23 dynamic sin = np.sin;
24 Console.WriteLine(sin(5));
25
26 double c = np.cos(5) + sin(5);
27 Console.WriteLine(c);
28 }
29 }
30 static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
31 {
32 return Assembly.LoadFile("C:/Python35/lib/site-packages/Python.Runtime.dll");
33 }
34 }
35 }
まぁなんというか、「開発時だけそうしたい」とかなら #ifdef
みたいな条件付コンパイルでも仕掛けときたい気はするわね。確か C# はそれ、出来たよな?