FortranでC言語のダブルポインタを受け取ってコマンドライン引数を取得する

# 概要
Fortran Advent Calendar [14日目の記事](https://qiita.com/implicit_none/items/1c33fc6c8daf50da28f0)において，ポインタ変数がもつアドレスをインクリメントする方法について説明しました．そして，[まとめ](https://qiita.com/implicit_none/items/1c33fc6c8daf50da28f0#%E3%81%BE%E3%81%A8%E3%82%81)で

> C言語のコマンドライン引数`char **argv`をFortranから読めるようになります．

と書きました．この記事では，その具体的なやり方について説明します．[黒魔術](https://qiita.com/implicit_none/items/1c33fc6c8daf50da28f0)は使わず，FortranとC言語の相互運用，C言語のダブルポインタに対する理解を深めることも目的とします．

本記事では，実行したコマンド全体（オプション付き）を**コマンドライン引数**あるいは**引数全体**とよび，個別の引数（オプション）および実行ファイル名を統一的に**オプション**とよぶことにします．

なお，身も蓋もない話ですが，Fortranにはコマンドライン引数を取得する`get_command()`サブルーチンが存在します．これ以外にも，オプションの個数を取得する`command_argument_count()`関数と各オプションを取得するサブルーチン`get_command_argument()`もあります．C言語では`main`関数の引数としてコマンドライン引数が渡されますが，Fortranは任意のタイミングでコマンドライン引数を参照できるので，こちらの方が柔軟性があると思います[^arg]．

[^arg]: Fortran 2003まで標準規格に入っていなかったことを考えると，もっと使いやすくてもよいと思いますが．

## 使用環境
|Fortranコンパイラ(バージョン)|Cコンパイラ(バージョン)|
|:--|:--|
|intel Parallel Studio XE Composer Edition for Fortran (17.0.4.210)|Microsoft(R) C/C++ Optimizing Compiler (19.00.24234.1 for x86)|
|gfortran (7.3.0)|gcc (7.3.0)|

# アドレスをインクリメントする関数（再掲）
[以前の記事](https://qiita.com/implicit_none/items/1c33fc6c8daf50da28f0)で作った関数を示します．関数内では，引数として受け取ったポインタ変数`address`と，バイト数の増分`increment_bytes`を足しています．
`char`型に対するポインタ`char *`を用いることで1バイトずつインクリメントできるようにし，インクリメントするバイト数を引数で渡します．
戻り値もchar型のポインタ`char *`としていますが，もしかしたら`void *`でもよいかもしれません．

```C
extern "C" {
    char* incrementAddress(char *address, int increment_bytes) {
        return address + increment_bytes;
    }
}
```

Fortran側のインタフェースは次のように書きました．引数はどちらも値渡しとしています．FortranとC言語では，標準がポインタ渡しか値渡しかで異なるためです．アドレスをインクリメントする関数を無駄に複雑にしないために，C言語の都合にあわせています．`byte`の実引数は`sizeof()`を利用して決定します．

```Fortran
    interface
        type(c_ptr) function incrementAddress(address, byte) bind(c,name="incrementAddress")
            use,intrinsic :: iso_c_binding
            implicit none
            type(c_ptr)        ,intent(in),value :: address
            integer(c_intptr_t),intent(in),value :: byte
        end function incrementAddress
    end interface
```

下のプログラムでは，C言語のポインタ変数がもつアドレスを変化させ，そのポインタ変数をFortranのポインタ変数に変換して参照しています．

```Fortran
program main
    use, intrinsic :: iso_c_binding
    implicit none
    
    interface
        type(c_ptr) function incrementAddress(address, byte) bind(c,name="incrementAddress")
            use,intrinsic :: iso_c_binding
            implicit none
            type(c_ptr)        ,intent(in),value :: address
            integer(c_intptr_t),intent(in),value :: byte
        end function incrementAddress
    end interface
            
    type(c_ptr) :: addr        ! C言語のポインタ変数
    integer(c_intptr_t) :: inc ! アドレスの増分

    character(:),allocatable,target :: str ! 走査する文字列

    character,pointer :: ptr_s ! C言語のポインタ変数を変換して1文字を参照するためのポインタ変数
    
    str = "abcd"//c_null_char

    inc = sizeof(c_null_char) ! character型のバイト数
    addr = c_loc(str)         ! 文字列strの先頭アドレス

    ! Null文字が出てくるまで，先頭から1文字ずつアドレスをずらしていく
    call c_f_pointer(addr,ptr_s)
    do while( ptr_s(1:1) /= c_null_char )
        print *,ptr_s(1:1)
        addr = incrementAddress(addr, byte=inc)
        call c_f_pointer(addr,ptr_s)
    end do
end program main
```

これを実行すると，一つずつ文字と数字が表示されます．

```console
 a
 b
 c
 d
```

# C言語からのFortranの手続呼出し
前節の例では，C言語のポインタ変数を介してFortranの文字列のアドレスを参照していました．今回は，C言語が主で，C言語のプログラム中からFortranの手続（サブルーチン/関数）を呼出し，呼び出された手続の中でコマンドライン引数を読むようにします．

C言語側から呼ばれるサブルーチンを`f_main`と名付け，その内容を記述します．今回はコマンドライン引数を読むことが目的であるため，引数として`argc`と`argv`を設けます．

```Fortran
    subroutine f_main(argc, argv) bind(c,name="f_main")
        use,intrinsic :: iso_c_binding
        implicit none

        integer(c_int),value      :: argc           ! オプションの個数
        type(c_ptr)   ,intent(in) :: argv(0:argc-1) ! オプション（文字列）を指すポインタの配列

    end subroutine f_main
```

配列を0開始にしているのは，`command_argument_count()`および`get_command_argument()`，あるいはC言語と動作を一致させるためです．

## C言語側から見える名前の設定

サブルーチン名の後ろにある`bind(c, name = "f_main")`は，インタフェースとは逆で，C言語側に見える名前を設定します．かつては，C言語からFortranのサブルーチンを呼ぶには，サブルーチン名を全てを大文字として取り扱う必要がありました[^cases]が，それはもう昔のお話です．

[^cases]: Fortranで小文字を使ってサブルーチン名を定義しても，C言語からは全て大文字で見えるので，C言語から呼ぶときは全て大文字にしなければならない，ということです．

## 仮引数の型

C言語との相互利用において，仮引数の型宣言は少し厄介です．C言語の`int argc`に対応する`argc`はコマンドを含むオプションの個数を表しており，C言語でもその値を参照します．Fortranに渡すときも，値渡しが自然です．`argc`を値渡しとするために，value属性を付けています．

`argv`はC言語の`char **argv`あるいは`char *argv[]`に対応しているので，それに見合った型を用いる必要があります．`char **argv`は**各オプションを表す文字列へのポインタの配列**です．Fortranで引数の文字列を扱えるようにするために，このポインタの配列を渡すことになります．

FortranとC言語の引数の対応は理解するのが厄介なのですが，「C言語からFortranにメモリアドレスを渡すと，そのメモリアドレスを1段階辿って値が渡されている」と見なすことができます．ものすごくざっくりと考えて，C言語から`&`付きでFortranに渡した変数は，Fortran側ではC言語における通常の（`&`無しの）変数として扱われる，と見なすことができます．

下のプログラムを例にすると，`int`型変数`a`の**メモリアドレス**を渡すと，Fortranのサブルーチン内ではアドレスが辿られて`a`の**値**が参照できるようになると見なせます[^note]．

```C
char a='a';
f_addr(&a); //aのメモリアドレス&aを渡すと，Fortran側で値aが参照できるようになる
```
```Fortran
subroutine f_addr(a) bind(c,name=f_addr)
    use,intrinsic :: iso_c_binding
    implicit none
    character :: a
end subroutine f_addr
```

[^note]: あくまで理解のためにそう見なしているだけで，正確な説明ではありません．

配列の場合は，C言語ではその変数名で先頭要素のアドレスを参照できるので，`&`を付けずに関数に渡すとポインタ渡しになります．

```C
char  a[] = { 'a', 'b', 'c' };
char *b   = "abc";
f_array(a, 3); //配列の場合はaがa[0]のメモリアドレス（&a[0]）を表す
f_array(b, 4); //文字列でも理屈は同じ
```
```Fortran
subroutine f_array(a,n) bind(c,name="f_array")
    implicit none
    
    character            :: a(n)
    integer(c_int),value :: n
end subroutine f_array
```

さて，文字列の配列に近づいてきました．C言語はFortranと大きく異なり，多次元配列は存在しません．形式的に2次元配列を宣言できますが，宣言した変数はポインタの配列となり，配列の各要素が1次元配列の先頭要素を指すメモリアドレスを持ちます．この事実に合うようにFortran側の仮引数の型を決めるとすると，次のようなポインタの配列を利用することになります[^ptr_array]．

```Fortran
subroutine f_dp(a,n) bind(c,name="f_dp")
    use,intrinsic :: iso_c_binding
    implicit none
    
    type(c_ptr)          :: a(n)
    integer(c_int),value :: n
end subroutine f_dp
```

[^ptr_array]: Fortranにポインタの配列はありませんが，派生型`type(c_ptr)`の配列はC言語のポインタ変数の配列となります．

ポインタの配列であっても，変数名で先頭要素のアドレスを参照できることは変わりません．ポインタの配列`char **argv`の変数名`argv`は，先頭要素を指すメモリドレス`&argv[0]`を参照するので，Fortranのサブルーチンには`argv`を渡します．

```C
char *argv[] = { "./a.out", "-h", "-D" };
printf("%p\n", &argv[0]); // 008FFED4
f_dp(&argv[0],3);         // 1文字目argv[0]のメモリアドレス&argv[0]を渡すと，Fortran側でargv[0]が参照できるようになる

printf("%p\n", argv);     // 008FFED4
f_dp(argv,3);             // &argv[0]と同じ

printf("%p\n",  argv   ); // 008FFED4
printf("%p\n", &argv[0]); // 008FFED4
printf("%p\n", &argv[1]); // 008FFED8
printf("%p\n", &argv[2]); // 008FFEDC
```

このようにして**ポインタの配列**の先頭アドレスを渡して**派生型`type(c_ptr)`の配列**で受け取り，Fortranのポインタ変数へ変換することで参照します．

## C言語の`main`関数

C言語側では，外部関数を呼び出すためにプロトタイプ宣言を行います．Fortranのサブルーチンは値を返さないので，C言語では関数の戻り値を`void`とします．実引数は`argc, argv`と単純に渡せるようにし，実引数の値が渡されるかメモリアドレスが渡されるかは，Fortranの仮引数の型宣言で決定します．

```C
extern "C" void f_main(int, char **); //プロトタイプ宣言

int main(int argc, char **argv) {

    f_main(argc, argv);

    return 0;
}

extern "C" {
    char* incrementAddress(char *address, int increment_bytes) {
        return address + increment_bytes;
    }
}
```

# 引数全体からのオプションの取り出し
コマンドライン引数全体の文字列から，個別のオプションを取り出します．仮に

```console
$ ./a.out -h -D
```
として実行すると，コマンドライン引数`argv`には下図のように文字列が納められます．
![memorymap](https://qiita-image-store.s3.amazonaws.com/0/224040/4245740a-2eb6-9ca6-11d0-0a76bd32bb62.png)

この文字列から個別にオプションを取り出すために，次のような手順を考えます．

1. 1番目のオプション（文字列）のメモリアドレスを，1文字目からNull文字`\0`が現れるまでインクリメントして文字数を数える．
1. オプションの長さがわかったら，その長さの文字型配列（`char []`）をFortranの文字列に変換する．
1. `argc`個のオプションを取り出すまで手順1.,2.を繰り返す．

```Fortran
    type(c_ptr) :: addr
    character,dimension(:),pointer :: c
    integer(c_intptr_t),parameter :: inc = sizeof(c_null_char) ! アドレスの増分

    integer :: lenOpt, opt
    type(c_arg),allocatable :: arg(:) ! 個別のオプションを保持するための派生型

    allocate(arg(0:argc-1))

    do opt = 0,argc-1
        addr   = argv(opt) ! opt番目のオプションの先頭アドレス

        ! Null文字を検出するまでメモリを走査して文字数をカウント
        lenOpt = 0
        call c_f_pointer(addr, c, [1])
        do while(c(1) /= c_null_char)
            lenOpt = lenOpt+1
            addr = incrementAddress(addr, byte=inc)
            call c_f_pointer(addr, c, [1])
        end do

        ! 文字の配列を文字列に変換
        allocate(character(lenOpt) :: arg(opt)%v) ! 長さlenOptの文字列
        call c_f_pointer(argv(opt), c, [lenOpt])  ! lenOpt要素の文字の配列
        arg(opt)%v = transfer(c, arg(opt)%v)

        print *,arg(opt)%v
    end do
```

ここで，`type(c_arg)`は，自動再割付け文字列`v`を成分に持つ派生型です．

```Fortran
    type,public :: c_arg
        character(:),allocatable :: v
    end type c_arg
```


# コンパイルと実行
C言語のソースを`main.cpp`，Fortranのソースを`f_main.f90`として，個別にコンパイル，リンクします．完全なソースファイルは[付録](#付録2ソースファイル)に示しておきます．

```console
$ gcc -c main.cpp
$ gfortnra -c f_main.f90
$ gcc -lgfortran *.o
```
WindowsでVisual Studioを利用する場合は，Visual C++のコンソールアプリケーションのプロジェクトを作成し，そこにFortranのスタティックライブラリのプロジェクトを追加します．

前節の例と同じ2個のオプションをつけて実行します．

```console
$ ./a.out -h -D
 ./a.out
 -h
 -D
```

```console
D:\>a.exe -h -D
 a.exe
 -h
 -D
```

コマンドライン引数全体が個別の3個のオプションに分解されていることが確認できます．

# まとめ
C言語のポインタは闇．

# 付録1 Fortran標準の手続によるコマンドライン引数の取得
Fortranにはコマンドライン引数全体を取得するサブルーチン`get_command()`，オプションの個数を取得する`command_argument_count()`関数，各オプションを取得するサブルーチン`get_command_argument()`もあります．

|関数|機能|
|:--|:--|
|`get_command([command = コマンドライン引数, length = コマンドライン引数の長さ, status=取得状態])`|コマンドライン引数全体を取得する<br>`command`引数が指定された場合，実行コマンドも含む全てのコマンドライン引数を文字列として取得する<br>`length`引数が指定された場合，コマンドライン引数の文字列の長さを取得する<br>`status`は，コマンドライン引数を正常に取得できれば0，文字列の変数がコマンドライン引数よりも短ければ-1，それ以外で正常に取得できなかった場合は正の値|
|`command_argument_count()`|実行コマンドを**含まない**オプションの個数を標準種別の整数型で返す|
|`get_command_argumet(number=オプション番号, [value = オプション, length = オプションの長さ, status=取得状態])`|指定された番号（`0`$\le$`number`$\le$`command_argument_count()`）のオプションを取得する（0は実行コマンド）<br>`value`引数が指定された場合，指定されたオプションを文字列として取得する<br>`length`引数が指定された場合，指定されたオプションの文字列の長さを取得する<br>`status`は，オプションを正常に取得できれば0，文字列の変数がオプションよりも短ければ-1，それ以外で正常に取得できなかった場合は正の値|

引数`command`, `length`, `value`は省略可能ですが，現実的な使い方としてすべてを省略することはないでしょう．

個別のオプションを取得するには，まず`command_argumet_count()`でオプションの個数を取得し，`do`ループの中で`get_command_argument()`によって各オプションの長さを取得した後，その長さの文字列を動的に割り付けて`get_command_argument()`でオプションを取得するのが定石です．

```Fortran
subroutine f_main() bind(c,name="f_main")
    use,intrinsic :: iso_c_binding
    implicit none

    integer :: argc
    integer :: opt,lenOpt
    character(:),allocatable :: argv
    type(c_arg),allocatable :: arg(:) ! 個別のオプションを保持するための派生型

    ! 全コマンドライン引数を取得する場合
    call get_command(length = lenOpt)
    allocate( character(lenOpt) :: argv)
    call get_command(command = argv)
    print *,argv

    ! 個別のオプションを取得する場合
    argc = command_argument_count() ! 実行コマンドを含まないオプションの個数
    allocate(arg(0:argc))           ! 0は実行コマンド
    do opt = 0,argc
        call get_command_argument(number=opt,length=lenOpt)

        allocate(character(lenOpt) :: arg(opt)%v)
        call get_command_argument(number=opt,value=arg(opt)%v)

        print *,arg(opt)%v
    end do

end subroutine f_main
```

[付録2](#付録2ソースファイル)に示した`f_main.f90`内のサブルーチン`f_main`をで置き換え，Cから引数なしで呼び出すと，以下の結果が得られました．

```console
D:\>a.exe -h -D
 a.exe  -h -D
 a.exe
 -h
 -D
```

## gfortranの場合
gfortranでは，コンパイルには成功しますが，実行ファイルを作成する段階でエラーが出でます．`f_main`内で`get_command()`を呼び出すだけでも以下のエラーが表示されます．

```console
f_main.o: 関数 `f_main' 内:
f_main.f90:(.text+0x46d): `_gfortran_get_command_i4' に対する定義されていない参照です
collect2: error: ld returned 1 exit status
```

オブジェクトファイルから実行ファイルを作成するコマンドを`gcc -lgfortran *.o`から`gfortran *.o`に変更すると，実行ファイルは無事作成できますが，コマンドライン引数は取得できません．引数の長さ`length`は0ですが，取得状態`status`も0になっているので，何かエラーが生じているわけではありません．

`f_main`をFortranのメインルーチンから呼んだ時は正しく引数が取得できているので，メインルーチン以外でも`get_command`等は実行できるようです．

gfortranでは，少なくともメインルーチンをFortranで書いたプログラムでないと，`get_command`等でコマンドライン引数を取得できないのでしょうか？よくわかりません．

# 付録2　ソースファイル

```C:main.cpp
extern "C" void f_main(int, char **);

int main(int argc, char **argv) {

	f_main(argc, argv);

	return 0;
}

extern "C" {
	char* incrementAddress(char *address, int increment_bytes) {
		return address + increment_bytes;
	}
}
```

```Fortran:f_main.f90
module f_routine
    use,intrinsic ::  iso_c_binding
    implicit none
    private
    public f_main

    ! 個別のオプションを保持するための派生型
    type,public :: c_arg
        character(:),allocatable :: v
    end type c_arg

    interface
        type(c_ptr) function incrementAddress(address, byte) bind(c,name="incrementAddress")
            use,intrinsic :: iso_c_binding
            implicit none
            type(c_ptr)        ,intent(in),value :: address
            integer(c_intptr_t),intent(in),value :: byte
        end function incrementAddress
    end interface
    
    contains

    subroutine f_main(argc, argv) bind(c,name="f_main")
        use,intrinsic :: iso_c_binding
        implicit none

        integer(c_int),value      :: argc
        type(c_ptr)   ,intent(in) :: argv(0:argc-1)

        type(c_ptr) :: addr
        character,dimension(:),pointer :: c
        integer(c_intptr_t),parameter :: inc = sizeof(c_null_char) ! アドレスの増分

        integer :: lenOpt, opt
        type(c_arg),allocatable :: arg(:) ! 個別のオプションを保持するための派生型

        allocate(arg(0:argc-1))

        do opt = 0,argc-1
            addr   = argv(opt) ! opt番目のオプションの先頭アドレス

            ! Null文字を検出するまでメモリを走査して文字数をカウント
            lenOpt = 0
            call c_f_pointer(addr, c, [1])
            do while(c(1) /= c_null_char)
                lenOpt = lenOpt+1
                addr = incrementAddress(addr, byte=inc)
                call c_f_pointer(addr, c, [1])
            end do

            ! 文字の配列を文字列に変換
            allocate(character(lenOpt) :: arg(opt)%v) ! 長さlenOptの文字列
            call c_f_pointer(argv(opt), c, [lenOpt])  ! lenOpt要素の文字の配列
            arg(opt)%v = transfer(c, arg(opt)%v)

            print *,arg(opt)%v
        end do

    end subroutine f_main
end module f_routine
```