IronPythonを遅延バインディングで呼び出してみた(ver3.4.0の場合)

---
## 初めに

「[IronPythonを遅延バインディングで呼び出してみた](https://qiita.com/tomoki0sanaki/items/a426758d8d668b3e2beb)」の続き
IronPyhton2.7系列から、今度はIonPython3.4系列にも挑戦してみた。

---
## 静的バインディング

まずは、静的バインディング

普通に、VisualStudioで「参照」をすれば、IronPython2.7.x系のコードで、.NETアプリ(c#)から、Pyhtonコードを呼び出すことができた。

何もコードを書き換えなくていいのは、**良い♪**。

参照するDLLは以下

- [IronPython3.4.0インストールディレクトリ]\IronPython.dll
- [IronPython3.4.0インストールディレクトリ]\Microsoft.Scripting.dll

---
## 遅延バインディングへの道1(GetTypesメソッドの罠)

いよいよ、遅延バインディング

サンプルコードは「[IronPythonを遅延バインディングで呼び出してみた](https://qiita.com/tomoki0sanaki/items/a426758d8d668b3e2beb)」にあるものをそのまま使って、改造していく。

これもそのままOKって感じでイケるんじゃん!?...と思いきや

![IrinPython340a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/5d50245b-cad7-f257-4f8e-5766d3781376.png)

IronPython.dllのAssenblyクラスのGetTypes()メソッドが、Typeの配列を返さないらしい。....orz

ふと、for文の上と中のGetType()メソッドの引数が、<b>&quot;Hosting.Python&quot;</b>と<b>&quot;IronPython.Hosting.Python&quot;</b>で異なることを発見。

```c#:form1.csの一部
Type type = lib.GetType("Hosting.Python");
if (type == null) {
    type = lib.GetType("IronPython.Hosting.Python");
}
if (type == null) {
    Type[] typeHako = null;
    typeHako = lib.GetTypes(); // GetTypes() はIronPython3.4.0の場合はエラーとなる
    for (int i = 0; i < typeHako.Length; i++) {
        if (typeHako[i].FullName == "IronPython.Hosting.Python") {
            type = typeHako[i];
            break;
        }
    }
}
```
こうすることで、問題解決。

---
## 遅延バインディングへの道2(ModuleResolveイベントで撃沈)

次は、

![IrinPython340b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/63e5ef2d-18ed-6243-bd0b-8212a480d3f4.png)

というエラー。
IronPython.dllと同じディレクトリ下のMicrosoft.Scripting.dllが見つからない(ロードしてくれない)ようだ。...orz

IronPython.dllと同じディレクトリなんだからMicrosoft.Scripting.dllも一緒にロードしてくれよ。と思いつつ...orz

DLLの検索パスをいじれらなきゃダメ!?...いやいやアプリケーションプログラマに検索パスいじるとかハードル高すぎでしょ、、、、などと、いろいろと調べてみると、System.Reflection.Assemblyには**ModuleResolveイベント**というモジュールのロードに失敗したら呼ばれるイベントがあるらしい。....これが使えないかと、こんなコードに修正。

ちなみに、[APIリファレンスはここ](https://learn.microsoft.com/ja-jp/dotnet/api/system.reflection.assembly.moduleresolve?view=net-7.0)

つまり、↓のような関数を定義しておいて・・・

```c#:form1.csの一部
private static Module evModuleResolve(object sender, ResolveEventArgs e)
{
    Assembly lib = Assembly.LoadFile("C:\\Program Files\\IronPython 3.4\\Microsoft.Scripting.dll"); // ← ここにブレイクポイントを設置してそもそも呼ばれるかどうか確認する
    Module m = lib.Modules.First();
    return m;
}
```

ここに**ブレイクポイント**を設置して・・・

```c#:form1.csの一部
lib.ModuleResolve += new ModuleResolveEventHandler(evModuleResolve);
```

な感じで、ModuleResolveイベントハンドラに割り当てて・・・

```c#:form1.csの一部
MethodInfo createEngine = type.GetMethod("CreateEngine", new Type[] { }, null);
```
↑のところのエラーで、先ほどのイベントハンドラを呼んでくれるかどうか・・・

・・・むむむ・・・

結論としては、イベントハンドラが呼ばれることはなかった(ブレイクポイントで止まらなかった)...orz

まぁ、ロード対象は、モジュール(Module)じゃ違うような、、、アセンブリ(Assenbly)のロード失敗には何かないかな!?・・・

---
## 遅延バインディングへの道3(AssemblyResolveイベントの希望)

さらに探していると、System.AppDomainとかいうクラスがあり、それにAssemblyResolveイベントハンドラというものがあるらしい。

ちなみに、[APIリファレンスはここ](https://learn.microsoft.com/ja-jp/dotnet/api/system.appdomain.assemblyresolve?view=net-7.0)

ということで、↓のような関数を定義しておいて・・・

```c#:form1.csの一部
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
    Console.WriteLine("Resolving...");　// ← ここにブレイクポイントを設置してそもそも呼ばれるかどうか確認する
    return null;
}
```

ここに**ブレイクポイント**を設置して・・・

```c#:form1.csの一部
AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveEventHandler);
```

な感じで、AssemblyResolveイベントハンドラに割り当てて・・・

さて・・・

```c#:form1.csの一部
MethodInfo createEngine = type.GetMethod("CreateEngine", new Type[] { }, null);
```
↑のところのエラーで、先ほどのイベントハンドラを呼んでくれるかどうか・・・

・・・結論としては、イベントハンドラ、呼ばれた!!

![IrinPython340c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/182822/9f72bbe0-fb57-8347-ea24-806d5817fde1.png)

こんな感じでイベントハンドラが呼ばれている♪

---
## 遅延バインディングへの道4(AssemblyResolveイベントを整理整頓)

ということで、定義した関数を↓のように修正

```c#:form1.csの一部
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
    Assembly liblib = null;
    String IronPythonDir = "C:\\Program Files\\IronPython 3.4\\";
    // IronPython以外のロードエラーには関わりたくないので、スルーしたい
    if (args.RequestingAssembly.Location.StartsWith(IronPythonDir) == true) {
        // args.Nameは
        // 「Microsoft.Scripting, Version=1.3.3.0, Culture=neutral, PublicKeyToken=7f709c5b713576e1」
        // みたいな感じだから、カンマの前までを抽出する
        String[] hako = args.Name.Split(new char[] { ',' });
        String str = IronPythonDir + "\\" + hako[0] + ".dll";
        // IronPythonのインストールディレクトリ以下にDLLが存在する時だけロードする
        if (File.Exists(str) == true) {
            liblib = Assembly.LoadFile(str);
        }
    }
    return liblib;
}
```

・・・結論としては、Pythonスクリプトが実行された・・・ので、ヨシ!!

最後に、ハードコーディングされている「C:\\Program Files\\IronPython 3.4\\」については、インストール先ディレクトリを保持しているレジストリから取ってくるようにして完成・・・というわけです。


---
## 遅延バインディングへの道5(AppDomain)

そもそも、しがないアプリケーションプログラマがAppDomainをいじっていいのか!?
とか、
(「[これ](http://rocketeer.dip.jp/sanaki/free/free119.htm)」と「[これ](http://rocketeer.dip.jp/sanaki/free/free140.htm)」)は、スクリプト機能は、プラグインとして本体EXEとは分離して別DLLにしているんだけど、、、
そういう状態でも、AppDomainいじっていいのかな!?
まぁ、(「[これ](http://rocketeer.dip.jp/sanaki/free/free119.htm)」と「[これ](http://rocketeer.dip.jp/sanaki/free/free140.htm)」)は、私一人で開発しているので、問題になるようなことはないかもしれないけど・・・!?

もしかして、
(「[これ](http://rocketeer.dip.jp/sanaki/free/free119.htm)」と「[これ](http://rocketeer.dip.jp/sanaki/free/free140.htm)」)はプラグイン機能として、IPlugin.dllという独立したDLLなので、この中でAppDomainを新規に作り出した方がいいのかな!?

AppDomainについて記事を見つけた→「[連載：明解.NETテクノロジ アプリケーション・ドメイン Application Domain](https://atmarkit.itmedia.co.jp/fdotnet/technology/glossary01/glossary01_02.html)」

---
## まとめ

なんとか、IronPython3.4.x系でも、バージョンごとにコンパイルしたバイナリを事前用意しなくてよくなった。
(2023年6月!?ぐらいに、ver3.4.1が出ていたので、いつの間にかにver3.4.x系もマルチバージョンになっていた)

さらに、IronPython2.7.x系とIronPython3.4.x系を同一バイナリに出来たのはうれしい。

---
## 以上