Pythonから.NETを呼び出す方法とついでにその逆も

# はじめに

Pythonで開発をしている中で一部.Net向けに提供されているDLLを呼び出す必要があり，その方法について調べました．
Pythonと.NETというとIronPythonが思い浮かびますが，今回はふつうにCPythonから使える[Python for .NET](https://pythonnet.github.io)を採用しました．

# Python for .NET

今回の目的そのままですが，Pythonのコードから.NETのコードを呼び出すことができます．
またその逆に，C#のコードからPythonを呼び出すこともでるようです．（Python for .NETって名前からはこっちのほうがしっくりくる？？）

# 開発環境

今回試した開発環境です．

* Windows 10 Pro
* Python 3.6.5
* Visual Studio Community 2017 15.6.6

# Pythonのコードから.NETを呼び出す

まずはもともとの目的の，Pythonのコードから.NETを呼び出す方法をです．

## インストール

pipからインストールできます．

```console
> pip install pythonnet
```

## 標準のクラスライブラリを呼び出す

試しにFormアプリケーションのエントリーポイントの記述をまねて書いてみます．
`clr.AddReference`で必要なアセンブリを読み込み，.NETの名前空間をPythonのパッケージとしてimportして扱うことができます．

```py
import clr

clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import Application, Form

Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
Application.Run(Form())
```

<img width="214" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/45310/2d9b2dad-ef92-fd22-9d6b-1664b552fd10.png">

ジェネリックスは角括弧を使って書くことができます．以下はList<T>クラスの使用例です．

```py
import clr

clr.AddReference('System.Collections')
from System.Collections.Generic import List

lst = List[int]()
lst.Add(3)
lst.Add(2)
lst.Add(1)
lst.Sort()

for x in lst:
    print(x)
```

```console:実行結果
1
2
3
```

## 自作のコードを呼び出す

次は自作のクラスをPythonコードから呼び出します．C#のコードはサンプルとして以下のコードを使いました．

```cs
using System;

namespace MyClassNamespace
{
    public class Foo
    {
        private string text;
        public Foo(string text) => this.text = text;
        public void DoSomething(int count) => Console.WriteLine(new string('*', count).Replace("*", text));
        public void DoSomething(string prefix) => Console.WriteLine($"{prefix}: {text}");
    }
}
```

上記のコードをビルドした`MyClass.dll`をPythonのコードと同じディレクトリにコピーすると，以下のように読み込めます．
オーバーロードされているメソッドは`Overloads[type]`で呼び分けることができます．（ただし今回の例だと明示的に指定しなくてもうまく動作しました．）

```py
import clr

clr.AddReference('MyClass')
from MyClassNamespace import Foo

foo = Foo('ABC')
foo.DoSomething.Overloads[int](3)
foo.DoSomething.Overloads[str]('@@@')
```

```console:実行結果
ABCABCABC
@@@: ABC
```

# C#のコードからPythonを呼び出す

もともとの目的とは違うのですが，逆にC#側からPythonのコードを呼び出す方法についても簡単にまとめます．

## インストール

NuGetからインストールできます．ただPython 3.5までの対応のようです．

<img width="331" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/45310/4e671eb5-fb9f-6f0a-b6ce-72150a6966ed.png">

## Pythonのモジュールを呼び出す

GitHubのREADMEに紹介されていたコードです．
ですが自分の環境ではやはり動きませんでした．またPython 3.5が入った環境で動かす機会があれば追記します．

```cs
using System;
using System.Collections.Generic;
using Python.Runtime;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            using (Py.GIL())
            {
                dynamic np = Py.Import("numpy");
                Console.WriteLine(np.cos(np.pi * 2));

                dynamic sin = np.sin;
                Console.WriteLine(sin(5));

                double c = np.cos(5) + sin(5);
                Console.WriteLine(c);

                dynamic a = np.array(new List<float> { 1, 2, 3 });
                Console.WriteLine(a.dtype);

                dynamic b = np.array(new List<float> { 6, 5, 4 }, dtype: np.int32);
                Console.WriteLine(b.dtype);

                Console.WriteLine(a * b);
                Console.ReadKey();
            }
        }
    }
}
```

```console:実行結果
ハンドルされていない例外: System.DllNotFoundException: DLL 'python35' を読み込めません:指定されたモジュールが見つかりません。 (HRESULT からの例外:0x8007007E)
   場所 Python.Runtime.Runtime.Py_IsInitialized()
   場所 Python.Runtime.Runtime.Initialize()
   場所 Python.Runtime.PythonEngine.Initialize(IEnumerable`1 args, Boolean setSysArgv)
   場所 Python.Runtime.PythonEngine.Initialize(Boolean setSysArgv)
   場所 Python.Runtime.PythonEngine.Initialize()
   場所 Python.Runtime.Py.GIL()
```