はじめに
Juliaを用いて数値計算をする際に、速度の向上などを目的として、一部の処理をFortranに行わせたい場合があります。Juliaには、Fortranのsubroutineを呼び出すための関数がデフォルトで用意されていて、このようなことは簡単に実行できるとされています。しかしながら、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コードを考えます。
module test_module
implicit none
contains
subroutine greet
write(*, *) "Hello."
end subroutine
end module test_module
このモジュール自身に問題がないことを確認するために、別のfortranプログラムからsubroutineを呼び出してみます。
program main
use test_module
implicit none
call greet
end program main
このプログラムは
>gfortran greet.f90 main.f90
によってコンパイルできます。これを実行すると
>a.exe
Hello.
のようになり、意図した通りの結果が得られます。
次に、このsubroutineをJuliaから呼び出すことを試みます。
そのためにまず、greet.f90
から共有ライブラリを作成します。
>gfortran greet.f90 -fPIC -shared -o greet.dll
生成されたgreet.dll
の中身をnm
コマンドで見てみます。
>nm greet.dll > log
log
を開き、subroutine名であるgreet
で検索をかけると、___test_module_MOD_greet
という文字列がヒットします。これがいわば「正式な」subroutine名で、Julia側から呼ぶためにはこれを指定する必要があります。ちなみにこの文字列は処理系に依存するようですので、必ずしもこの例のようにはならないかもしれません。(これを解決する方法はありますが、本筋から離れるので後で説明します。)
次にJuliaのコードとして、以下のようなものを用意します。
function greet()
product = ccall((:___test_module_MOD_greet, "./greet.dll"),
Nothing,
())
end
greet()
これを実行すると
>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
という何やら闇の深そうなメッセージが現れてしまいました。
(追記)同様の問題を解説している記事がありました。
原因
上のエラーは、gccが32bit版であることが原因でした。gccの選択が重要であることは、この方が指摘されています。(明示的には書かれていませんが、同じ問題に直面したのではないかと思います。)今一度gccのバージョンを確認してみると
>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
というメッセージから原因を探る他ないのですが、情報量が少なすぎて困ってしまいます。同様の現象はどうやら早くから知られていたようで、このページで議論された後、ad hocな解決策らしきものが提案されていました。しかしながらここで述べられていることは、fortranコードをコンパイルしてライブラリを作成する工程をJuliaで自動化しているに過ぎず、本質的な解決になっていないように思われます。(少なくとも私の問題は解決しませんでした。)
Win10上でFortranコードを呼び出すまでの手順
以下では、Windows10上にJuliaがインストールされていて、その他はまっさらな状態を仮定します。
gccのインストール
ここで述べる手順は、このページに書かれている方法とまったく同じです。
- MingW-W64-buildsをダウンロードし、インストーラーを実行する。
- 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のバージョンを確認して、
>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コードのコンパイル
公式ドキュメントにある通り、-fPIC
と-shared
オプションをつけてコンパイルします。
>gfortran greet.f90 -fPIC -shared -o greet.dll
生成されたgreet.dll
の中身をnm
コマンドで見てみます。
>nm greet.dll > log
logを開き、subroutine名であるgreetで検索をかけると、__test_module_MOD_greet
という文字列がヒットします。先程のときと比べて`test'の前のアンダースコアの数が一つ減っていました。
Juliaから呼び出す
subroutine名以外に変更点はありませんが、念の為コードサンプルを示します。
function greet()
product = ccall((:__test_module_MOD_greet, "./greet.dll"),
Nothing,
())
end
greet()
これを実行して
>julia greet_for_64bitgcc.jl
Hello.
となれば成功です。お疲れさまでした。
補足1: 共有ライブラリの拡張子
Windowsなので共有ライブラリのファイル名を何となくgreet.dll
にしましたが、別にgreet.so
でも構いません。
補足2: subroutineの名称
上記のやり方では、subroutine名が__test_module_MOD_greet
のように長ったらしいものになる上、処理系にも依存してしまいます。これを回避する方法として、subroutineにBIND(C)属性を指定する方法があります。これについては以下のページで解説されています。
-JuliaからFortranのsubroutineを呼び出す(Qiita)
-JuliaからFortranのsubroutine を呼び出す
今の例では、fortran, juliaのコードはそれぞれ
module test_module
implicit none
contains
subroutine greet() bind(c, name = 'greet')
write(*, *) "Hello."
end subroutine
end module test_module
function greet()
product = ccall((:greet, "./greet.dll"),
Nothing,
())
end
greet()
のようにすれば良くなります。