PythonからFortranを使う――CtypesとISO_C_bindingの利用

# 1. はじめに
この記事では、Pythonの標準モジュールであるCtypesと、Fortran2003の機能であるIso_C_bindingを組み合わせて用いることで、PythonからFortranのコードを利用する方法を紹介する。Ctypes・Iso_C_bindingはどちらも言語の標準の機能となっており、環境によってコードを書き換えなくても済むことが期待できる。(後述するが、残念ながらどうしても環境依存の問題は色々と起こり得る。)

- 7/31に７節を修正・加筆、及びまとめに加筆

## 1.1 進め方について
基本的なFortranやPythonの知識があるものとして説明する。Fortranの"ISO_C_binding"やPythonの"Ctypes"の使い方についてもわかりやすいドキュメントがいくつか存在するのでそちらを参照していただきたい。本記事ではそれらを"連携"させる際のポイントに焦点を当てたい。

また、GitHubに[サンプルコード](https://github.com/ymzkd/Ctypes-ISO_C_binding-Samples)を上げた。本文ではコードを部分的に取り出しているがサンプルコードはPythonを実行できFortranをコンパイルできる環境があれば実際に動かして試せるように書いたつもりなので参考にしていただきたい。(example5と6はNumpyも利用しています。)

# 2. ライブラリのロードと関数の呼び出し
基本的なPython+Fortranの利用例として、以下のFortranのSubroutineをPythonから呼び出してみる。

```fortran:Fortran側(example1.f90)
use ISO_C_binding
implicit none

subroutine print_hello_for_c() bind(C, name="print_hello")
  !DEC$ ATTRIBUTES DLLEXPORT :: print_hello_for_c
  
  print *, "hello world in fortran"

end subroutine print_hello_for_c
```
このSubroutineを含む共有ライブラリを呼び出すPythonのコードは次のようになる。

```python:Python側(example1.py)
import ctypes

lib = ctypes.CDLL("./example1.so")
lib.print_hello()
```
Fortran側では、ISO_C_bindingの読み込みにより手続きにC互換性をもたせるために必要な機能が扱えるようになる。このサンプルでは、Subroutineにbind属性を付与してC互換性を確保している。また、bindの付与時にnameを指定することで、ライブラリ外部から参照する際の関数名を明示的に指定している。

Python側ではCtypesの<code>CDLL()</code>で共有ライブラリを読み込むことでライブラリの関数の呼び出しが可能な状態になる。

また、<code>!DEC$ ATTRIBUTES DLLEXPORT :: print_hello_for_c</code>という一文についてはintelのコンパイラを用いてライブラリを作成した時に手続きや変数を外部に公開する際に必要になる。ここでは、<code>DLLEXPORT</code>に指定するのはFortranの手続き名であるということを示すために、手続き名とbindのnameで指定している参照名は意図的に違うものにしている。以降のサンプルでは省略する。

# 3. データ型/プロトタイプ宣言
## 3.1 データ型
Fortran、PythonそれぞれでC言語のデータ型に互換性があるようなデータ型の作成が可能である。Fortranは[ISO_C_bindingで使えるC互換の型](http://www.nag-j.co.jp/fortran/tips/tips_InteroperabilityWithC.html#KeyInformation)を、Pythonは[Ctypesで使えるC互換の型](https://docs.python.jp/3/library/ctypes.html#fundamental-data-types)をそれぞれ用いればデータをやり取りする際に互換性を確保できる。

以下にいくつかの代表的なデータ型の対応を示す。

#### 基本データ型
```fortran:fortran変数(example2.f90)
integer(C_int) :: int_val
```
```python:Python変数(example2.py)
int_val = ctypes.c_int()
```
Fortranでは基本データ型はISO_C_bidingによって提供されるC互換の型を用いる事が出来る。Pythonでは、Ctypesによって利用可能な基本データ型のインスタンスとして作成する。

### 構造体
```fortran:fortran構造体(example2.f90)
! 構造体の定義
type, bind(C) :: day
  integer(C_int) :: month
  integer(C_int) :: day
end type day
! 構造体の作成
type(day) :: today
```
```python:Python構造体(example2.py)
# 構造体の定義
class day(ctypes.Structure):
    _fields_ = [("month", ctypes.c_int), ("date", ctypes.c_double)]
# 構造体の作成
today = day()
```
Fortran側では構造体に<code>bind(C)</code>属性を付与しておく、構造体で用いる基本データ型にはISO\_C\_bindingの型を用いる。

Python側では<code>Ctypes.Structure</code>を継承したクラス内の<code>\_fields\_</code>変数において構造体メンバーを登録する。<code>\_fields\_</code>変数は、変数名とデータ型を成分とするタプルのリストで構成する。

### 配列

```fortran:fortran配列(example2.f90)
integer(C_int), parameter :: array_length = 10
integer(C_int) :: array(array_length)
```
```python:Python配列(example2.py)
# 配列型の定義
array_length = 10
int_arr_shape = ctypes.c_int * array_length
# 配列の作成
int_arr = int_arr_shape()
```

Ctypesでは配列は基本データ型に成分数を掛け合わせて"配列型"を作成し、配列型のインスタンスを生成することで配列を作成できる。この配列型はこの例であれば<code>"c_int_Array_10"</code>という型になる。また、配列成分もc\_int型ではなく通常のint型となる。このようにctypesでは配列長が異なる配列は異なる型として扱われる。そのため後ほど取り上げるデータのやり取りの際に、任意長の配列を扱うには工夫が必要となる。

## 3.2 関数のプロトタイプ宣言

前節のサンプルコードでは省略していたが、Python側でFortran関数のインターフェイスをプロトタイプ宣言することが可能。[<code>argtypes</code>](https://docs.python.jp/3/library/ctypes.html#specifying-the-required-argument-types-function-prototypes)に関数の引数の型を指定しておくと関数を呼んだ際に型チェックが行われる。

[<code>restype</code>](https://docs.python.jp/3/library/ctypes.html#return-types)には関数の返り値のデータ型を指定する。SubroutineはCのvoid関数に対応しているのでSubroutineを利用する場合は<code>restype=None</code>として返り値がないことを明示する。指定しない場合はCtypesで読み込んだ関数はint型を返すものとして扱われる。

例として下記Fortran関数のargtypes, restypeを考える。

```fortran:Fortran関数インターフェイス部分(example2.f90)
function add(val_a, val_b) bind(C, name="add")
    integer(c_int), intent(in) :: val_a, val_b
    integer(c_int) :: add
```
Fortranが引数のポインタ渡しを基本としていることに気をつけて以下のように書ける。

```python:Pythonプロトタイプ宣言(example2.py)
lib.add.restype = ctypes.c_int
lib.add.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)]
```
Ctypesでは任意のデータ型のポインタ型は<code>ctypes.POINTER()</code>メソッドにベースとなるデータ型を渡すことで作成できる。

# 4. Fortranにデータを渡す
基本的には前節で説明した互換性のあるデータ型を用いることでPythonとFortranの間でデータのやりとりが出来る。Fortran手続きの引数は基本的にポインタ渡しなので、ポインタを渡す必要がある。データのポインタは[<code>ctypes.byref()</code>](https://docs.python.jp/3/library/ctypes.html#ctypes.byref)によって生成できる。

Fortranに様々なデータ型のデータを渡すサンプルを示す。構造体や配列のデータの内容は前節のサンプルコードで示したものを用いている。

```fortran:Fortran側(example3.f90)
  subroutine input(val_int, arr_length, val_arr, val_type) bind(C, name="input")

    integer(c_int), intent(in) :: val_int
    integer(c_int), intent(in) :: arr_length
    integer(c_int), intent(in) :: val_arr(arr_length)
    type(day_type), intent(in) :: val_type

  end subroutine input
```
```python:Python側(example3.py)
lib = ctypes.CDLL("./example3.so")

# プロトタイプ宣言
lib.input.restype = None
lib.input.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), \
                    ctypes.POINTER(ctypes.c_int), ctypes.POINTER(day_type)]

# キャストによるポインターの作成
input_arr_pointer = ctypes.cast(input_arr, ctypes.POINTER(ctypes.c_int))

# 関数の呼び出し
lib.input(ctypes.byref(input_val), ctypes.byref(ctypes.c_int(array_length)), \
              input_arr_pointer, ctypes.byref(input_day))
```
配列を渡す際のデータ型の扱いに工夫を要する。固定長配列であれば<code>argtypes</code>に配列型を登録しておくことで関数呼び出し時の型チェックでも問題が生じない。一方で、任意長の配列を扱う機会は非常に多いものの先述したようにctypesでは異なる長さの配列は異なるデータ型になるので、配列長が変更になるたびにargtypesを書き換えなければならない。

そこで、C言語のように配列先頭のデータ型のポインタ型をargtypesに登録しておき、関数を呼び出す際には配列先頭のデータのポインタを渡すというやり方を使う。配列先頭データのポインタは<code>[ctypes.cast()](https://docs.python.jp/3/library/ctypes.html#ctypes.cast)</code>にて取得できる。

ポインタは配列と同じようにインデックスでアクセスできるが、C言語と同様にデータの格納されている範囲を把握しておかないと意味を成さないメモリー領域を参照してしまうので注意する必要がある。一方でctypesの配列オブジェクトは配列長の情報を含んでおり、ループのイテレータとしても利用できる。

# 5. Fortranからデータを受け取る
Fortranでは、処理したデータを受け取る際に、返り値が無く、入力した変数に関数内でデータを書き込んで結果を得るSubroutineと、返り値として結果を受け取るFunctionがある。ここでは両者を対比しながらサンプルを示す。

## 5.1 スカラーデータの受け取り
サンプルとして1次元配列を入力として受け取って合計値を計算するという例を取り上げる。

```python:Python側入力データ(example4.py)
# 入力データの用意
array_length = 10
input_arr_shape = ctypes.c_int * array_length
input_arr = input_arr_shape(*range(array_length))
input_arr_pointer = ctypes.cast(input_arr, ctypes.POINTER(ctypes.c_int))
```

### Subroutineの利用
Subroutineを用いるときは、結果のデータを格納する変数を関数の呼び出し側で用意して引数として渡す必要がある。

```fortran:Fortran側(example4.f90)
subroutine sum_all_sub(arr_length, array, result) bind(C, name="sum_all_sub")
  integer(c_int), intent(in) :: arr_length
  integer(c_int), intent(in) :: array(arr_length)
  integer(c_int), intent(out) :: result

  result = sum(array(:))

end subroutine sum_all_sub
```
```python:Python側(example4.py)
# Subroutineのプロトタイプ宣言
lib.sum_all_sub.restype = None
lib.sum_all_sub.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), \
                               ctypes.POINTER(ctypes.c_int)]

# Subroutineの利用
result_val = ctypes.c_int()
lib.sum_all_sub(ctypes.byref(ctypes.c_int(array_length)), \
                input_arr_pointer, ctypes.byref(result_val))
```

### Functionの利用
Functionでは返り値を格納する変数を関数の呼び出し側で用意する必要はないが、正しい型の引数を受け取るために<code>restype</code>に型の指定を正しく行う必要がある。

```fortran:Fortran側(example4.f90)
function sum_all_func(arr_length, array) bind(C, name="sum_all_func")
  integer(c_int), intent(in) :: arr_length
  integer(c_int), intent(in) :: array(arr_length)
  integer(c_int) :: sum_all_func

  sum_all_func = sum(array(:))

end function sum_all_func
```
```python:Python側(example4.py)
# Functionのプロトタイプ宣言
lib.sum_all_func.restype = ctypes.c_int
lib.sum_all_func.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)]
                
# Functionの利用
result_val = lib.sum_all_func(ctypes.byref(ctypes.c_int(array_length)), \
                              input_arr_pointer)
```
このサンプルでは、<code>lib.sum_all_func.restype = ctypes.c_int</code>にて返り値の型を指定している。

## 5.2 配列データの受け取り
サンプルとして次の1次元配列とスカラー値を引数として渡し、配列成分全てにスカラー値を加算した配列を結果として受け取るという処理を行う。

```python:Python側(example4.py)
# 入力データ
offset = 3
array_length = 10
input_arr_shape = ctypes.c_int * array_length
input_arr = input_arr_shape(*range(array_length))
input_arr_pointer = ctypes.cast(input_arr, ctypes.POINTER(ctypes.c_int))
```

### Subroutineの利用
Subroutineで結果が配列となる処理を行うためには、結果を受け取る配列を関数の呼び出し側で用意して引数として渡す必要がある。

```fortran:Fortran側(example4.f90)
subroutine shift_all_sub(offset, arr_length, array, result_array) bind(C, name="shift_all_sub")
  integer(c_int), intent(in) :: offset
  integer(c_int), intent(in) :: arr_length
  integer(c_int), intent(in) :: array(arr_length)
  integer(c_int), intent(out) :: result_array(arr_length)

  result_array(:) = array(:) + offset

end subroutine shift_all_sub
```
```python:Python側(example4.py)
# プロトタイプ宣言
lib.shift_all_sub.restype = None
lib.shift_all_sub.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), \
                              ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)]
# Subroutine用結果受け取り配列の用意
result_arr = input_arr_shape()
result_arr_pointer = ctypes.cast(result_arr, ctypes.POINTER(ctypes.c_int))

# Subroutineの利用
lib.shift_all_sub(ctypes.byref(ctypes.c_int(offset)), ctypes.byref(ctypes.c_int(array_length)), \
          input_arr_pointer, result_arr_pointer)
```

### Functionの利用
Functionで返り値を配列とするためにはちょっとした工夫が必要となる。Fortran側は、返したい配列のための領域を動的に確保し、その配列のポインタを返すという処理を行う。一方でPython側は、返り値がポインタとなるため<code>restype</code>には配列データ型のポインタを指定する必要がある。

また、関数を呼び、処理が終わった後もFotran側で動的に確保した領域をしっかり管理する必要がある。Fortranのライブラリ側で確保したメモリーの領域はPythonのデコンストラクタによって解放処理されることはない。利用が終了した配列はしっかりと解放する必要がある。

<!--- Functionで結果が配列となる処理を行う際には、restypeに正しい型を宣言しておく必要がある。-->
<!--- 返り値はFortran側では<code>type(c_ptr)</code>型を用いる必要がある。bind属性を付与した関数は、返り値が配列とすることができない。-->
<!--- Fortranライブラリ内でallocateした領域については、Python側でメモリーの領域が解放されることはない。そのため、自分で領域を開放する必要がある。-->
<!--- 配列の領域を開放する際には、Iso_C_bindingのC_ptr型を利用し、一旦Fortranのポインタに配列を割り当て、そのポインタをdeallocateする必要がある。(詳細後述)-->

```fortran:Fortran側(example4.f90)
! 処理を行う関数
function shift_all_func(offset, arr_length, array) bind(C, name="shift_all_func")
  integer(c_int), intent(in) :: offset
  integer(c_int), intent(in) :: arr_length
  integer(c_int), intent(in) :: array(arr_length)
  type(C_ptr) :: shift_all_func

  integer(c_int), pointer :: work_array(:)

  allocate(work_array(arr_length))
  work_array(:) = array(:) + offset

  shift_all_func = C_loc(work_array)

end function shift_all_func

! 配列解放用の関数
subroutine delete_array(arr_length, array) bind(C, name="delete_array")
  integer(c_int), intent(in) :: arr_length
  type(c_ptr), value :: array
  integer(c_int), pointer :: work_array(:)

  call C_F_pointer(array, work_array, [arr_length])
  deallocate(work_array)
  
  ! Nullの代入
  array = C_NULL_PTR

end subroutine delete_array
```

```python:Python側(example4.py)
# プロトタイプ宣言
lib.shift_all_func.restype = ctypes.POINTER(ctypes.c_int)
lib.shift_all_func.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), \
                               ctypes.POINTER(ctypes.c_int)]
# 処理結果と配列の受け取り
result_arr = lib.shift_all_func(ctypes.byref(ctypes.c_int(offset)), \
    ctypes.byref(ctypes.c_int(array_length)), input_arr_pointer)
# 領域の解放
lib.delete_array(ctypes.byref(ctypes.c_int(array_length)), result_arr)
```

Functionによって配列を返り値とする際には、①Fortranから配列を受け取る、②使い終わった領域を解放する、という処理が必要になる。

①についてはC互換性のあるFortranの手続きは配列そのものを返り値とすることができないためFortranの手続きの中で配列を動的に確保(allocate)し、そのポインタを返り値として返す。

Cのポインタに対応するデータ型としてISO_C_bindingではc_ptrという構造体が用意されている。c_ptr型のデータを返り値とする。<code>shift\_all\_func = C_loc(work_array)</code>の一文でCのポインタを返り値として返している。<code>C_loc()</code>は、pointer属性が付与されたFortran配列からC言語向けのポインタを取得する事ができる。

②について、上記サンプルではdelete\_arrayというSubroutineで領域を解放している。ライブラリ内で動的に確保したメモリー領域はライブラリ内で解放する必要がある。そこで一旦Cのポインタとして配列ポインタを受け取り、Fortranのpointer配列に変換する。そのための処理を行っているのが<code>call C_F_pointer(array, work_array, [arr_length])</code>の一文になる。<code>C_F_pointer</code>はCのポインタからFortranのポインタを作成する事ができる。

以上の点を踏まえ、delete_array関数では次のような手順で配列の領域を解放している。

1. 配列をCのポインタとして受け取る。array引数にvalue属性がついているが、前述した通り、Fortranはポインタ渡しが基本なので、Cのポインタを受け取る際には「ポインタのポインタ」を渡さなければならなくなる。そのためCのポインタそのものを受け取る際にはvalue属性を付けて値渡しとする。
1. C_F_pointerによってCの配列ポインタをFortranのpointer配列に変換している。
1. 上記手順によって作成されたpointer配列をdeallocateすることで確保した領域を解放している。
1. 入力されたCのポインタの支持先をnullにしている。  ISO_C_bindingではC_ptr型のに対応するnull値になる<code>C_NULL_PTR</code>という定数が用意されているので、Cのポインタの支持先をnullにしておきたい場合は<code>C_NULL_PTR</code>を代入する。

# 6. Numpyの利用
Numpyは、Ctypesを連携して利用できるように<code>[numpy.ctypeslib](https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.ctypeslib.html)</code>モジュールにてCtypesの基本的な機能とNumpyと連携するためのモジュールを提供している。<code>[ctypeslib](https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.ctypeslib.html)</code>モジュールを利用することで、Numpy配列をそのままCtypesでライブラリの関数に入力できたり、受け取った結果をNumpy配列のように利用することが可能になる。

下のサンプルでは、Fortran側に入力されたランダムな値からなる配列のうち正の成分のみを抽出した配列を結果として出力する処理を行い、結果の配列ポインタをCtypes側に返している。<code>numpy.ctypeslib</code>の<code>ndpointer</code>を用いてNumpyの配列を引数としてFortran側に入力できるようにプロトタイプ宣言を行う。また、Fortran側から結果として返ってきた配列ポインタから<code>as_array</code>を用いてNumpy配列として使えるオブジェクトを生成する。Numpyを利用してもFortran側で確保したデータの領域はFortranライブラリで解放しなければならないことは変わらないということに注意が必要。

```fortran:Fortran側(example5.f90)
  use ISO_C_binding
  implicit none
  type, bind(C) :: result
    integer(c_int) :: len
    type(c_ptr) :: arr
  end type result

contains

  function extruct_plus(arr_length, array) bind(C, name="extruct_plus")
    integer(c_int), intent(in) :: arr_length
    integer(c_int), intent(in) :: array(arr_length)
    type(result) :: extruct_plus

    integer(c_int) :: plus_count
    integer(c_int), pointer :: work_array(:)

    plus_count = count(array(:)>=0)
    allocate(work_array(plus_count))
    work_array(:) = pack(array(:), mask=(array(:)>=0))

    extruct_plus%len = plus_count
    extruct_plus%arr = C_loc(work_array)

  end function extruct_plus
```
```python:Python側(example5.py)
# 構造体の定義
class result(ctypes.Structure):
    _fields_ =[("len", ctypes.c_int), ("arr", ctypes.POINTER(ctypes.c_int))]

# Functionのプロトタイプ宣言
lib.extruct_plus.restype = result
lib.extruct_plus.argtypes = [ctypes.POINTER(ctypes.c_int), np.ctypeslib.ndpointer(dtype=np.int32, ndim=1)]

# 領域解放関数のプロトタイプ宣言
lib.delete_array.restype = None
lib.delete_array.argtypes = [ctypes.POINTER(ctypes.c_int), np.ctypeslib.ndpointer(dtype=np.int32, ndim=1)]

# 入力データの作成
array_length = 10
arr_np = np.random.randint(low=-100, high=100, size=array_length, dtype=np.int32)

# Functionの利用
result = lib.extruct_plus(ctypes.byref(ctypes.c_int(array_length)), arr_np)
result_len = result.len
result_vec = np.ctypeslib.as_array(result.arr, shape=(result_len,))

# 領域の解放
lib.delete_array(ctypes.byref(ctypes.c_int(result_len)), result_vec)
```
<code>ndpointer</code>はargtypesにて<code>np.ctypeslib.ndpointer(dtype=np.int32, ndim=1)</code>と指定している。指定したデータタイプと次数のNumpy配列であればそのまま関数に渡せるように宣言している。ndpointerではdtype、shape、ndimが指定可能で、それぞれデータ型、配列形状、次数を指定するものになっている。指定しないこともできるので適宜使い分けたい。

<code>as\_array</code>を<code>np.ctypeslib.as\_array(result.arr, shape=(result_len,))</code>の一文で用いている。配列ポインタをNumpy配列そのもののように扱えるオブジェクトを作成できる。ただし、上述したようにライブラリ側で確保したデータ領域はPythonからは解放できないという制限はnumpyを用いたとしても同様なので領域は自分で解放する必要がある。

# 7. コンパイラによって生じる問題(7/31修正・加筆)
Git-Hubに上げたサンプルコードのexample4とexample5のdelete_arrayを呼ぶとintelのコンパイラではdeallocateに失敗して下記のエラーになる。

```
forrtl: 致命的なエラー (173): DEALLOCATE に渡されたポインターが割り付け解除できないオブジェクトを指しています。
```
(7/31加筆)
初回投稿時には、linuxではgfortranでコンパイルし、windowsではintel fortranでコンパイルしていたので問題がOS依存かと勘違いしてしまったが、リサーチと少し検証した結果コンパイラ依存と思われるので補足する。しかも、Fortranの仕様と関係があるらしいので曖昧でない問題とも思われる。関連する投稿がIntelのフォーラムに上がっていた。"[how to declare a fortran function that returns a c_ptr](https://software.intel.com/en-us/forums/intel-visual-fortran-compiler-for-windows/topic/279486)"

該当部分は下の通りで、Fotran2008の仕様でFortranで一旦割り付けた領域のポインタを外部に渡し、Fortranにポインタとして再び渡ったものをdeallocateすることは出来ないということが書かれている気がする。
>You are taking the address of an allocatable, and then later on trying to deallocate it through a fortran pointer to the same object. That's not permitted in Fortran (see 6.7.3.3 in F2008) - otherwise the compiler may lose track of the status of the allocatable variable. (My previous comments about using an allocatable were in the context of stopping the object from disappearing when the procedure terminates.)

[Fortran2008の仕様[PDF]](http://www.j3-fortran.org/doc/year/10/10-007.pdf)を確認したが語学力のせいか、指摘されている6.7.3.3が今の問題に該当するのかどうか分からなかった。gfortranではFortran2008の上記に該当する仕様がまだ反映されていないために仕様に反していても問題が生じなかったようだ。

いずれにしても初回投稿時に下に書いたサンプルは有効なようで上のIntelのフォーラムでも同様な解決方法(データをモジュール変数とする)が取られている。
(7/31加筆終了)

モジュール側にモジュール変数としてデータを用意し、そのデータの中身をSubroutineで写し取るというやり方ならintelのコンパイラでもDLL側で確保した領域をしっかりと解放しつつ結果を受け取ることが出来る。すこし無理やりかもしれないが、この方法なら処理結果のデータ構造やサイズがわからない場合にも柔軟に対応できる。

```fortran:Fortran側(example6.f90)
  ! 構造体の定義
  type, bind(C) :: report
    integer(c_int) :: len
  end type report

  integer, pointer :: extruct_array(:)
  
contains

  ! 処理と結果のデータ数の算定
  function extruct_plus(arr_length, array) bind(C, name="extruct_plus")
    integer(c_int), intent(in) :: arr_length
    integer(c_int), intent(in) :: array(arr_length)
    type(report) :: extruct_plus

    extruct_plus%len = count(array(:)>=0)
    allocate(extruct_array(extruct_plus%len))
    extruct_array(:) = pack(array(:), mask=(array(:)>=0))

  end function extruct_plus

  ! モジュール配列の引数配列への転写
  subroutine get_array(arr_length, array) bind(C, name="get_array")
    integer(c_int), intent(in) :: arr_length
    integer(c_int), intent(inout) :: array(arr_length)

    array(:) = extruct_array(:)

  end subroutine get_array

  ! モジュールポインタ配列の解放
  subroutine delete_array() bind(C, name="delete_array")

    deallocate(extruct_array)

  end subroutine delete_array
```
```python:Python側(example6.py)
# 構造体の定義
class report(ctypes.Structure):
    _fields_ =[("len", ctypes.c_int)]

# 初期データの作成
array_length = 10
arr_np = np.random.randint(low=-100, high=100, size=array_length, dtype=np.int32)

# 入力データ処理(Fortranモジュールにデータが作成される)
result = lib.extruct_plus(ctypes.byref(ctypes.c_int(array_length)), arr_np)

# 結果受け取り配列の作成
result_len = result.len
result_vec = np.zeros(result_len, dtype=np.int32)

# 結果の受け取り
lib.get_array(ctypes.byref(ctypes.c_int(result_len)), result_vec)

# Fortran側データの削除
lib.delete_array()
```

実際に利用する際にはPython側で一連の処理を関数としてまとめるとよいかも。

# まとめ
- PythonのCtypes, FortranのISO\_C\_bindingというそれぞれの標準機能を組み合わせて用いることでFortranのPythonからの利用時に環境への依存が少なくすることが出来る。
- データのやり取りの際にはargtypes, restypeをしっかり書く。
- 関数の内部で動的にメモリーを確保した場合には領域の管理が必要で環境によっては問題が生じる。
- 出来る限りSubroutineで、呼び出し側で用意した引数に対して結果のデータを代入することでライブラリ内でのデータ領域の動的確保をなるべく避ける。
- 複雑なデータ構造や可変配列をライブラリ側から受け取る場合、内部変数にデータを確保した上で、形状やデータサイズについての情報だけを呼び出し側に一旦渡し、それに基づいて呼び出し側でデータ領域を確保してライブラリ側でデータを転写するという遠回りがトラブル回避に有効な場合もある。

7/31加筆内容について

- Fortranの2008までの仕様では、C言語との複雑なデータのやり取りはなかなか難しい。
- Fortran2008の補足という形で[TS 29113](https://www.ibm.com/support/knowledgecenter/ja/SSAT4T_16.1.0/com.ibm.xlf161.lelinux.doc/language_ref/ts29113.html)というFortran-Cの相互運用性についての仕様が策定されているらしい。Fortranのデータ型そのものとして扱えるオブジェクト(allocatableなarrayなども)をCで作れるというものらしい。制限があるものの11月にFinalのDraftが出る予定のFortran2018ではこれがさらにまた発展するらしいので勉強してみたい。