Python PythonNet
===
###### tags: `Python`
> * 紀錄如何在 Python 使用 PythonNet 呼叫 .net framework dll (C#, C++/cli)
> * 若要在 C# 中透過 PythonNet 呼叫 python .pyd 可參考 [C# PythonNet](https://hackmd.io/@AvalonChang/H1idvcYZJl)
## :package: **安裝 pythonnet package**
* PythonNet 可透過 pip install 或 conda install 等方式安裝至虛擬環境
>安裝完成後可在 python 內 import clr 測試, 若可正常 import 代表安裝成功
```python!
import clr
```
---
## :computer: **調用 dll**
### 1. 添加 dll 路徑
若 dll 所在路徑不在當前 python 的工作目錄中, 建議直接將 dll 路徑加入到 Python 工作目錄中
> 若 dll 有相依其他 dll , 但相依 dll 不在 Python 工作目錄中也會導致程式報錯
```python!
import os
import sys
# 將 DLL 所在目錄加入 Python 工作目錄
dll_dir = 'your_dll_directory'
if dll_dir not in sys.path:
sys.path.append(dll_dir)
```
### 2. 將 dll 加入 clr 的 reference 中
由於剛才已將 dll 所在目錄加入工作目錄中, 這裡的 'your_dll_name' 可以是不含路徑和副檔名的檔案名稱
> 這裡也可以直接填入完整的 dll 路徑,
```python!
clr.AddReference('your_dll_name')
```
### 3. import 所需的 dll 類別
* 假設 dll 內有個名為 "XXX_sapce" 的 namespace, 想要呼叫其中的 class XXX_class, 在 python 中就可以這麼做
```python!
from XXX_space import XXX_class
xxx = XXX_class() # 假設 constract 時不需要輸入參數
```
* 若 dll 內有很多需要使用的功能, 也可以一次將 dll 內容全部 import
```python!
from XXX_space import *
xxx = XXX_class()
yyy = YYY_class()
```
## :memo: Note
經過上述, 基本上已經確定能導入 dll 功能在 Python 中使用
但由於程式語言的差異, 有些語法或變數結構無法直接對應, 這邊僅列出遇過的情況
### a. 變數結構
在 .net framework 中有 System.Int32, System.String 等變數結構
在 Python 中也可以透過 import System 的方式使用
```python!
from System import String, Int32, Array, Byte # etc, ...
```
同理, 當 dll interface 需要 .net List 的結構時就可以在 Python 中宣告出 .net framework 的結構
```python!
from System.Collections.Generic import List
dot_net_str_list = List[String]() # 宣告一個 .net framework 的 string list
dot_net_str_list.Add('something_you_need') # 如同 python list 的 append
xxx.func(dot_net_str_list) # 呼叫一個需要 .net framework string list 的 function
```
### b. call by reference
* 在 C# 中存在 call by reference 的語法
```csharp!
void call_by_ref(ref int value)
{
value = -1;
}
void main()
{
int val = 0;
call_by_ref(val);
Console.WriteLine(val); // 輸出會是 -1
}
```
但在 Python 中會得到下列結果
```python!
val = 0
call_by_ref(val)
print(val) # 輸出會是 0
```
這是由於 Python 傳遞參數的方式是 call by sharing, 對於 int 這類不可變類型會是類似 call by value 的處理方式
* 為了解決這個問題, PythonNet 會將修改後的 reference 以 return 的方式回傳
```python!
val = 0
val = call_by_ref(val)
print(val) # 輸出會是 -1
```
> 若函數本來就有回傳值, 或是不只一個 ref 參數
> 則會以 tuple 的方式回傳
---