Fortranの派生型とC言語の構造体の相互運用

# 概要
[Fortran Advent Calendarの４日目](https://qiita.com/implicit_none/items/12c5404a11e6c237d100)で，派生型の基本的な使い方についてまとめました．この記事は，派生型の少し進んだ使い方として，C言語との相互利用についてまとめました．

## 使用環境
|コンパイラ|バージョン|
|:--|:--|
|intel Parallel Studio XE Composer Edition for Fortran|17.0.4.210|
|PGI Visual Fortran for Windows|18.7|
|gfortran|7.3.0 (for Ubuntu on WSL)|

## 更新履歴
- 2018年12月8日　誤字修正


# 派生型と構造体の互換性
Fortranの派生型はC言語の構造体と同じ役割をしています．
C言語とFortranを相互利用していると，派生型をC言語の関数に渡す，あるいはC言語の構造体を手続の引数として受け取る場合があります．Fortranの型は，C言語に近いところもあれば，異なるところもあります．特に配列や文字列，動的割付が関係するところはかなり違います．相互利用に際しては，Fortranの派生型がC言語の構造体と互換性を有していることを保証しなければなりません．

派生型と構造体の互換性を保証するには，`bind(c)`属性を付与します．`bind(c)`属性を付けた場合，いくつかの制約が生じます．明確にコンパイルエラーが出るのは，最初の2項目です．特に，2個目はFortranの配列に慣れている人からすると，かなりの痛手です．

- `sequence`は使えません．
- `allocatable`あるいは`pointer`属性を持つ成分は定義できません．
- 整数および実数の種別を，C言語の型に対応した種別に変更します．それらの種別は`iso_c_binding`モジュールで定義されています．
- 文字列を文字の配列に変更します．また，終端にNULL文字を付ける必要が生じるので，配列要素数はFortranでの文字列の長さに+１文字必要になります．
- `allocatable`あるいは`pointer`属性を持つ成分は，すべてC言語のポインタ変数に相当する成分に置き換えます．

[4日目の記事](https://qiita.com/implicit_none/items/12c5404a11e6c237d100)でも取り上げた，人の情報を管理するための派生型`person`を，C言語と相互運用できるように修正していきます．

```Fortran
program main
    use,intrinsic :: iso_fortran_env
    implicit none
    
    type,bind(c) :: person ! bind(c)属性を付与してC言語の構造体との互換性を保証
        sequence           ! sequenceは使用不可
        character                         :: initial
        character(:),allocatable          :: name        ! allocatable属性を持つ成分は持てない
        integer(int32)                    :: age         ! kind(int32)をc_intへ置き換え
        integer(int32)                    :: birthyyyy   ! kind(int32)をc_intへ置き換え
        integer(int32)                    :: birthmm     ! kind(int32)をc_intへ置き換え
        integer(int32)                    :: birthdd     ! kind(int32)をc_intへ置き換え
        character(3)                      :: birthMonth  ! 文字列の長さは1（文字の配列へ置き換え）
        type(person),dimension(:),pointer :: ptrToPerson ! type(c_ptr)へ置き換え
    end type person
end program main
```

成分`name`は`character(4)`でしたが，`allocatable`属性が使えないことを示すために，自動再割付文字列としています．これをコンパイルするとエラーがたくさん出ます．


## C言語の型に対応した種別
FortranとC言語の相互運用のために，`iso_c_binding`モジュールで様々なパラメータと関数が定義されています．C言語の変数に対応した種別のうち，よく使う種別を表に示します．

|種別|C言語の型|種別の値|備考|
|:--|:--|:--:|:--|
|`c_int` | `int` |4[^c_int]|`integer`に相当|
|`c_float` | `float`|4|`real(real32)`に相当|
|`c_double` | `double` |8|`real(real64)`に相当|
|`c_long_double` | `long double` |16[^c_l_d_i]<br>8[^c_l_d_p]<br>12[^c_l_d_g]|拡張倍精度|

[^c_int]: 調査した環境での値です．
[^c_l_d_i]: Intelコンパイラ
[^c_l_d_p]: PGIコンパイラ
[^c_l_d_g]: gfortran

基本的には，整数型や単精度・倍精度実数型の種別はFortranと同じです．一方，Fortranで4倍精度といっていた型はC言語における拡張倍精度`c_long_double`に相当しており，この種別はコンパイラで大きく異なります．Intelコンパイラでは16，gfortranでは12です．PGIコンパイラは4倍精度実数に対応していないので，種別は定義していますがその値は倍精度と同じです．C言語の拡張倍精度は80 bit（10バイト）なので，最も近いのはgfortranです．つまり，gfortranは実数の種別として，4, 8, 12, 16を持っていることになります．

派生型`person`では整数型の成分があるので，それらの種別を置き換えます．種別は`iso_c_binding`で定義されているのでそれをuseし，不要になった`iso_fortran_env`を消去します．

```Fortran
program main
    use,intrinsic :: iso_c_binding
    implicit none
    
    type,bind(c) :: person
        character                         :: initial
        character(:),allocatable          :: name
        integer(c_int)                    :: age         ! kind(int32)をc_intへ置き換え
        integer(c_int)                    :: birthyyyy   ! kind(int32)をc_intへ置き換え
        integer(c_int)                    :: birthmm     ! kind(int32)をc_intへ置き換え
        integer(c_int)                    :: birthdd     ! kind(int32)をc_intへ置き換え
        character(3)                      :: birthMonth  
        type(person),dimension(:),pointer :: ptrToPerson 
    end type person
end program main
```

## C言語の文字列（文字の配列）
次に，Fortranの文字列をC言語の文字の配列に置き換えます．文字の配列と文字列はビット列としてみると同じなので，warningは出ますが，そのまま文字列を渡してしまうこともできます．ですが，FortranとC言語で文字列の終端の取り扱いが異なるため，文字の長さの修正が必要になります．

C言語では，文字列はNULL文字で終わります．そのため，長さ4の文字列を取り扱うためには，その4文字にNULL文字を加えた5文字とする必要があります．

```C
    char name[5];
    name[0] = 'A';
    name[1] = 'd';
    name[2] = 'a';
    name[3] = 'm';
    name[4] = '\0';
```
上の例では4文字の名前を取り扱っています．C言語では，配列は0から始まり，配列要素番号の上限は要素数-1です．そのため，`char name[4];`と宣言すると，0から3までの4文字分の要素を利用できますが，末尾1文字はNULL文字に充てる必要があります．

```Fortran
program main
    use,intrinsic :: iso_c_binding
    implicit none
    
    type,bind(c) :: person
        character                         :: initial
        character                         :: name(4+1)       ! 文字の配列へ置き換え，+1文字は終端のNULL文字用
        integer(c_int)                    :: age
        integer(c_int)                    :: birthyyyy
        integer(c_int)                    :: birthmm
        integer(c_int)                    :: birthdd
        character                         :: birthMonth(3+1) ! 文字の配列へ置き換え，+1文字は終端のNULL文字用
        type(person),dimension(:),pointer :: ptrToPerson
    end type person
end program main
```

文字列の相互運用のために，`iso_c_binding`モジュールでは，エスケープシーケンスが文字型のパラメータとして定義されています．NULL文字は`c_null_char`という名前で定義されています．

## C言語のポインタの利用
`pointer`属性を持つ成分は，C言語のポインタ変数に置き換えます．C言語のポインタ変数と互換性のある型として，派生型`type(c_ptr)`が定義されています．

派生型の最終的な形は以下のようになりました．

```Fortran
program main
    use,intrinsic :: iso_c_binding
    implicit none
    
    type,bind(c) :: person
        character      :: initial
        character      :: name(4+1)
        integer(c_int) :: age
        integer(c_int) :: birthyyyy
        integer(c_int) :: birthmm
        integer(c_int) :: birthdd
        character      :: birthMonth(3+1)
        type(c_ptr)    :: ptrToPerson     ! type(c_ptr)へ置き換え
    end type person
end program main
```

`type(c_ptr)`は，`private`属性の整数型`integer(c_intptr_t)`を成分に持つ派生型です．つまり，C言語のポインタと同じように，メモリアドレスのみを持つ整数型変数です．C言語では，`char *`のようにメモリアドレスの先にあるデータが何型かを特定できるようにポインタ変数を宣言しますが，Fortranの場合は`type(c_ptr)`が持つメモリアドレスを参照することはできませんし，そのアドレスの先に何型のデータがあるかを知ることもできません．変数のメモリアドレスを関数`c_loc()`を用いて`type(c_ptr)`型で取得することと，サブルーチン`c_f_pointer(cptr=C言語のポインタ変数もしくはメモリアドレス, fptr=Fortranのポインタ変数[,shape=配列形状])`でC言語のポインタ変数（もしくはメモリアドレス）をFortranのポインタに変換（もしくは結合）することはできます．

# 変更結果
Fortranでの処理がどのように変わるかを見たあと，C言語の相互利用について見てみます．Fortranのポインタについては別の機会にまとめて本記事にリンクを張るので，ここでは何か面倒な手間が増えているなという程度に認識しておいてください．

変更前のプログラムを示します．

```Fortran
program main
    use,intrinsic :: iso_fortran_env
    implicit none
    
    type :: person
        character      :: initial
        character(4)   :: name
        integer(int32) :: age
        integer(int32) :: birthyyyy
        integer(int32) :: birthmm
        integer(int32) :: birthdd
        character(3)   :: birthMonth
        type(person),dimension(:),pointer :: ptrToPerson
    end type person
    
    type(person),target :: a_classroom(3)

    a_classroom(1) = person("A", "Adam", 20, 1998, 12, 4, "Dec", a_classroom)
    a_classroom(2) = person("N", "Nick", 21, 1997, 11, 4, "Nov", a_classroom)
    a_classroom(3) = person("Z", "Zack", 22, 1996, 10, 4, "Oct", a_classroom)
    
    print *,a_classroom(1)%name ! Adam
    print *,a_classroom(2)%name ! Nick
    print *,a_classroom(3)%name ! Zack

    print *,a_classroom(1)%ptrToPerson(2)%name ! Nick
    print *,a_classroom(3)%ptrToPerson(2)%ptrToPerson(1)%ptrToPerson(3)%name ! Zack
end program main
```

## Fortranのみの運用
派生型に`bind(c)`属性を付与し，必要な変更を加えたのが下のプログラムです．特にポインタ変数`ptrToPerson`をC言語のポインタ変数に置き換えたので，プログラム中で利用するためにサブルーチン`c_f_pointer()`で変換しています．

```Fortran
program main
    use,intrinsic :: iso_c_binding
    implicit none
    
    type,bind(c) :: person
        character      :: initial
        character      :: name(4+1)
        integer(c_int) :: age
        integer(c_int) :: birthyyyy
        integer(c_int) :: birthmm
        integer(c_int) :: birthdd
        character      :: birthMonth(3+1)
        type(c_ptr)    :: ptrToPerson
    end type person
    
    type(person),target :: a_classroom(3) ! ポインタ変数から参照できるようにtarget属性を付与する
    type(person),dimension(:),pointer :: ptr_person
    
    a_classroom(1) = person("A", ["A","d","a","m",c_null_char], 20, 1998, 12, 4, ["D","e","c",c_null_char], c_loc(a_classroom))
    a_classroom(2) = person("N", ["N","i","c","k",c_null_char], 21, 1997, 11, 4, ["N","o","v",c_null_char], c_loc(a_classroom))
    a_classroom(3) = person("Z", ["Z","a","c","k",c_null_char], 22, 1996, 10, 4, ["O","c","t",c_null_char], c_loc(a_classroom))
      
    print *,a_classroom(1)%name ! Adam
    print *,a_classroom(2)%name ! Nick
    print *,a_classroom(3)%name ! Zack

    !ptrToPersonはただのメモリアドレスでしかないので，a_classroom(1)%ptrToPerson(2)%nameというアクセスはできない．
    call c_f_pointer(cptr=a_classroom(1)%ptrToPerson, fptr=ptr_person, shape=[3])
    print *,ptr_person(2)%name ! Nick
end program main
```

## FortranとC言語の相互運用
C言語と相互利用できる派生型が宣言できたので，C言語の関数に派生型変数を渡してみます．Cで定義した構造体をFortranの手続に渡すことも可能ですが，現実では古いFORTRANをC/C++に置き換えることが多いので，段階的にCへ移植していくことを想定しています．

さすがにFortranで定義した派生型をC側から利用する手段はないので，C側で構造体`person`とそれを初期化する関数`initPerson`を定義します．FortranはCと相互利用できるようになりましたが，C++の名前修飾（name mangling）には対応していないので，名前修飾を抑制します．文字列は`sprintf_s`で書き込んでいますが，C言語のお作法的によいやり方かはわかりません．よい方法があればご指摘ください．

```C
#include<stdio.h>
#include<string.h>

struct person {
	char initial;
	char name[5];
	int  age;
	int  birthyyyy;
	int  birthmm;
	int  birthdd;
	char birthMonth[4];
	struct person *ptrToPerson;
};

extern "C" {
	void initPerson(struct person *group, int num) {
		group[0].initial     = 'A';
		group[0].age         = 20;
		group[0].birthyyyy   = 1998;
		group[0].birthmm     = 12;
		group[0].birthdd     = 4;
		group[0].ptrToPerson = group;
		sprintf_s(group[0].name, "Adam");
		sprintf_s(group[0].birthMonth, "Dec");

		group[1].initial     = 'N';
		group[1].age         = 21;
		group[1].birthyyyy   = 1997;
		group[1].birthmm     = 11;
		group[1].birthdd     = 4;
		group[1].ptrToPerson = group;
		sprintf_s(group[1].name, "Nick");
		sprintf_s(group[1].birthMonth, "Nov");

		group[2].initial     = 'Z';
		group[2].age         = 22;
		group[2].birthyyyy   = 1996;
		group[2].birthmm     = 10;
		group[2].birthdd     = 4;
		group[2].ptrToPerson = group;
		sprintf_s(group[2].name, "Zack");
		sprintf_s(group[2].birthMonth, "Oct");
	}
}
```

これをコンパイルし，Fortranのソースをコンパイルするときにリンクします．FortranからCの関数を利用する場合，関数名の先頭にアンダースコア`_`を追加することでも呼べるのですが，現代のFortranでは，Cの関数にバインドされる手続のインタフェースを宣言します．そうすることにより，関数と手続の対応が取れ，引数のチェックが可能になります．あくまで仮引数と実引数の型をチェックするので，インタフェースでの仮引数の型はCの関数と同じでなければなりません．

```Fortran
interface
    {function, subroutine} Fortranの手続名([仮引数,仮引数,...]) bind(c, name="C言語の関数名")
        [import 派生型名] ! 同じルーチン内で派生型を宣言し，仮引数の型宣言に利用する場合に必要 
        implicit none
        [仮引数の変数宣言]
    end {function, subroutine}
end interface
```

関数の戻り値が`void`の場合は`subroutine`，それ以外の場合は`function`を用います．

手続のインタフェースと同じルーチンで派生型を定義している場合，インタフェース内でその派生型を参照できません．この問題を解決するためには，`import`で派生型名を読み込みます．

[Fortranのみの運用](#fortranのみの運用)のプログラムを，Cの関数を呼ぶように変更しました．Fortranでは，仮引数に何も属性を付けなければポインタ渡し（参照渡し）になりますが，`value`属性を付与すると値渡しになります．ポインタ渡しか値渡しかは，C言語の関数と対応するように決めなければなりません．これはよく間違えるところなので，相互運用する場合は注意が必要です．

関数`initPerson`では，`person`型のポインタと配列要素数`num`を渡しているので，それに対応するようにインタフェースを定義しています．そのため，実引数には`a_classroom`ではなく`a_classroom(1)`を渡しています．`a_classroom`と`a_classroom(1)`のメモリアドレスは同じですが，型としては別物なので区別が必要です．

```Fortran
program main
    use,intrinsic :: iso_c_binding
    implicit none
    
    type,bind(c) :: person
        character      :: initial
        character      :: name(4+1)
        integer(c_int) :: age
        integer(c_int) :: birthyyyy
        integer(c_int) :: birthmm
        integer(c_int) :: birthdd
        character      :: birthMonth(3+1)
        type(c_ptr)    :: ptrToPerson
    end type person

    interface
        subroutine initPerson(group,num) bind(c, name = "initPerson")
            use iso_c_binding
            import person ! 同一ルーチン内で定義したtype(person)をinterface内で利用するために必要
            implicit none
            type(person),intent(inout) :: group
            integer(c_int),value :: num
        end subroutine initPerson
    end interface
    
    type(person) :: a_classroom(3)
    type(person),dimension(:),pointer :: ptr_person
    
    call initPerson(a_classroom(1),size(a_classroom))
    print *,a_classroom(1)%name ! Adam
    print *,a_classroom(2)%name ! Nick
    print *,a_classroom(3)%name ! Zack

    !ptrToPersonはただのメモリアドレスでしかないので，a_classroom(1)%ptrToPerson(2)%nameというアクセスはできない．
    call c_f_pointer(cptr=a_classroom(1)%ptrToPerson, fptr=ptr_person, shape=[3])
    print *,ptr_person(2)%name ! Nick
end program main
```

# 関数名の取得
リンクするオブジェクトにどのような関数があるかを調べるには，WindowsでVisual Studio (C)をインストールしている場合はdumpbin.exeが利用できます．dumpbin.exeはVisual Cのインストールフォルダ以下のbinフォルダに置かれています．

dumpbin.exeをsymbolsオプション付きで実行すると，オブジェクトファイルのファイルフォーマット仕様に沿ってシンボルの一覧を表示します．色々出力されますが，その中に関数名`initPerson`を見つけることができます．

```shell
> dumpbin.exe /symbols オブジェクトファイル（.obj）
:
:
048 00000000 SECT10 notype ()    External     | _initPerson
:
:
```
先頭にアンダースコア`_`が付いています．Fortran 2003以前は，この関数のシンボル名称を利用して関数を呼んでいたということでしょう．

# まとめ
`iso_c_binding`，翼を授ける