Windows10でFortranのsubroutineをJuliaから呼び出す

# はじめに
Juliaを用いて数値計算をする際に、速度の向上などを目的として、一部の処理をFortranに行わせたい場合があります。Juliaには、[Fortranのsubroutineを呼び出すための関数](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#Calling-C-and-Fortran-Code-1 "ccall")がデフォルトで用意されていて、このようなことは簡単に実行できるとされています。しかしながら、Windows10上でやろうとしたときに思わぬ落とし穴があったので、ここにまとめておきたいと思います。

# 症状
問題が発生したのは以下の環境です。

- OS: Windows10 (64-bit)
- Juliaのバージョン: 1.3.1
- fortranコンパイラ: gfortran 8.1.0 (build by MinGW-W64, i686-posix-dwarf-rev0)

MinGW-W64をインストール後、gfortranのパスを通し終わっていると仮定します。以下のfortranコードを考えます。

```fortran:greet.f90
module test_module
    implicit none    

contains

    subroutine greet
        write(*, *) "Hello."
    end subroutine

end module test_module
```

このモジュール自身に問題がないことを確認するために、別のfortranプログラムからsubroutineを呼び出してみます。

```fortran:main.f90
program main
    use test_module
    implicit none
    
    call greet
end program main
```

このプログラムは

```shell-session
>gfortran greet.f90 main.f90
```

によってコンパイルできます。これを実行すると

```shell-session
>a.exe 
Hello.
```
のようになり、意図した通りの結果が得られます。

次に、このsubroutineをJuliaから呼び出すことを試みます。
そのためにまず、`greet.f90`から共有ライブラリを作成します。

```shell-session
>gfortran greet.f90 -fPIC -shared -o greet.dll
```

生成された`greet.dll`の中身を`nm`コマンドで見てみます。

```shell-session
>nm greet.dll > log
```

`log`を開き、subroutine名である`greet`で検索をかけると、`___test_module_MOD_greet`という文字列がヒットします。これがいわば「正式な」subroutine名で、Julia側から呼ぶためにはこれを指定する必要があります。ちなみにこの文字列は処理系に依存するようですので、必ずしもこの例のようにはならないかもしれません。（これを解決する方法はありますが、本筋から離れるので後で説明します。）

次にJuliaのコードとして、以下のようなものを用意します。

```julia:greet.jl
function greet()
    product = ccall((:___test_module_MOD_greet, "./greet.dll"),
                Nothing,
                ())
end

greet()
```

これを実行すると


```shell-session
>julia greet.jl
ERROR: LoadError: error compiling greet: could not load library "./greet.dll"
%1 is not a valid Win32 application.

Stacktrace:
（省略）
```

というエラーが発生します。`not a valid Win32 application`という何やら闇の深そうなメッセージが現れてしまいました。

（追記）同様の問題を解説している[記事](https://qiita.com/r9y9/items/e0567e2a21a5e3c36e51#%E3%83%8F%E3%83%9E%E3%82%8A%E3%81%A9%E3%81%93%E3%82%8D)がありました。

# 原因
上のエラーは、**gccが32bit版であることが原因**でした。gccの選択が重要であることは、[この方](https://myenigma.hatenablog.com/entry/2018/11/13/212347)が指摘されています。（明示的には書かれていませんが、同じ問題に直面したのではないかと思います。）今一度gccのバージョンを確認してみると

```shell-session
>gcc --version
gcc (i686-posix-dwarf-rev0, Built by MinGW-W64 project) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```
と表示されます。`i686`とあるので、確かにこれは32bit版であることが分かります。（そもそもコンピューターに詳しくないので、gccにも色々種類があるとか、i686が32bitを意味するとか知るよしもありませんでした。）

（注）`could not load library`と出ていることから、一見`greet.dll`が（何らかの理由で）Juliaから見えないことが原因かのようにも思われますが、カレントディレクトリを`LOAD_PATH`に追加するなどしても結果は変わりません。従って、`not a valid Win32 application`というメッセージから原因を探る他ないのですが、情報量が少なすぎて困ってしまいます。同様の現象はどうやら早くから知られていたようで、[このページ](https://discourse.julialang.org/t/how-to-make-a-c-function-compiled-by-myself-available-to-ccall/7972/20)で議論された後、[ad hocな解決策](https://www.codementor.io/@evalparse/making-compiling-c-functions-for-use-in-julia-on-windows-f9lwa5i43)らしきものが提案されていました。しかしながらここで述べられていることは、fortranコードをコンパイルしてライブラリを作成する工程をJuliaで自動化しているに過ぎず、本質的な解決になっていないように思われます。（少なくとも私の問題は解決しませんでした。）

# Win10上でFortranコードを呼び出すまでの手順
以下では、Windows10上にJuliaがインストールされていて、その他はまっさらな状態を仮定します。

## gccのインストール
ここで述べる手順は、[このページ](https://myenigma.hatenablog.com/entry/2018/11/13/212347)に書かれている方法とまったく同じです。

- [MingW-W64-builds](http://mingw-w64.org/doku.php/download)をダウンロードし、インストーラーを実行する。
- Settings: Specify setup settings. と聞かれるので、Architectureとして`x86-64`を選択する。その他はデフォルトのままでOK.
- あとはnextを連打してインストールを完了させる。
- 環境変数に`gfortran.exe`があるディレクトリ（例：`C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin`）を加える。
- システムを再起動する。

ターミナルでgccのバージョンを確認して、

```shell-session
>gcc --version
GNU Fortran (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```
と表示されれば成功です。

## Fortranコードのコンパイル
[公式ドキュメント](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/#Calling-C-and-Fortran-Code-1 "ccall")にある通り、`-fPIC`と`-shared`オプションをつけてコンパイルします。

```shell-session
>gfortran greet.f90 -fPIC -shared -o greet.dll
```

生成された`greet.dll`の中身を`nm`コマンドで見てみます。

```shell-session
>nm greet.dll > log
```

logを開き、subroutine名であるgreetで検索をかけると、`__test_module_MOD_greet`という文字列がヒットします。先程のときと比べて`test'の前のアンダースコアの数が一つ減っていました。

## Juliaから呼び出す
subroutine名以外に変更点はありませんが、念の為コードサンプルを示します。

```julia:greet_for_64bitgcc.jl
function greet()
    product = ccall((:__test_module_MOD_greet, "./greet.dll"),
                Nothing,
                ())
end

greet()
```

これを実行して

```shell-session
>julia greet_for_64bitgcc.jl
Hello.
```

となれば成功です。お疲れさまでした。

## 補足1: 共有ライブラリの拡張子
Windowsなので共有ライブラリのファイル名を何となく`greet.dll`にしましたが、別に`greet.so`でも構いません。

## 補足2: subroutineの名称
上記のやり方では、subroutine名が`__test_module_MOD_greet`のように長ったらしいものになる上、処理系にも依存してしまいます。これを回避する方法として、subroutineに[BIND(C)属性](https://www.nag-j.co.jp/fortran/fortran2003/Fortran2003_5_4.html)を指定する方法があります。これについては以下のページで解説されています。

-[JuliaからFortranのsubroutineを呼び出す(Qiita)](https://qiita.com/cometscome_phys/items/d098b00ab97f44916783)
-[JuliaからFortranのsubroutine を呼び出す](https://hydrocoast.jp/index.php?Julia/Fortran%E3%81%AEsubroutine%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97)

今の例では、fortran, juliaのコードはそれぞれ

```fortran:greet_cbinded.f90
module test_module
    implicit none    

contains

    subroutine greet() bind(c, name = 'greet')
        write(*, *) "Hello."
    end subroutine

end module test_module
```
```julia:greet_cbinded.jl
function greet()
    product = ccall((:greet, "./greet.dll"),
                Nothing,
                ())
end

greet()
```
のようにすれば良くなります。