概要
Libpq-Fortranというライブラリを作ったとき、CとFortranで相互に値を受け渡すためのinterface
文と引数宣言の仕方についてかなり嵌ったので、まとめを作ることにしました。
メインプログラムはFortranであることを前提に記述します。
C相互運用機能の基本的な使い方はNAG社のFortran Tip集: C との相互利用可能性の例を参照してください。
本稿では文字列の受け渡しをメインに取り扱います。
テスト環境:GCC 13.2.1
本編
Fortranから引数で文字列を渡す
#include <stdio.h>
void print_buf(const char*buf)
{
printf("%s\n", buf);
return;
}
program main
use :: iso_c_binding
implicit none
! C関数の宣言
interface
subroutine c_print_buf (buf) bind(c, name="print_buf")
import c_char
! 長さ1の文字型の配列として宣言する(assumed-size arrayが利用可能)
character(1, kind=c_char), intent(in) :: buf(*)
end subroutine print_buf
end interface
call c_print_buf('fuga'//c_null_char) ! 文字列の末尾にnull文字を付け加える。
end program
FortranのメインプログラムにCの関数を記述するinterface
文を追加します。ここでは文字列引数を長さ1
、kind=c_char
の文字の配列として宣言します。Cの引数宣言では文字型ポインタ(char*
)型として宣言します。Fortranからこの関数を呼び出す際にはかならずc_null_char
を末尾に付け加える必要があります。
Cから引数で文字列を受け取る
#include <string.h>
void hoge_into_buf(char**buf)
{
char src[] = "hoge\0";
strcpy(*buf, src);
return;
}
program main
use, intrinsic :: iso_c_binding
implicit none
! C関数の宣言
interface
subroutine c_hoge_into_buf(buf) bind(c, name="hoge_into_buf")
import c_ptr
type(c_ptr), intent(out) :: buf ! value属性をつけない
end subroutine hoge_into_buf
end interface
character(5), target :: buf_f = ''
type(c_ptr) :: ptr
ptr = c_loc(buf_f)
call c_hoge_into_buf(ptr)
print *, buf_f
end program main
ここではinterface
文で文字列の変数をtype(c_ptr)
として宣言し、value属性をつけないのがポイントです。
Fortran側で文字列のバッファの文字列変数を用意します。この変数にはtarget
属性をつけてc_loc
関数で使えるようにする必要があります。さらにc_ptr
型の変数を宣言し、c_loc
関数で文字列変数のアドレスを格納します。上でinterface
文に書いた通り、Cの関数にはこのポインタを引数に渡します。
Cから戻り値で文字列を受け取る
Cの関数から、戻り値としてポインタ(char*
型)を受け取り、Fortran側で文字列としてprint
します。
Cの関数return_char
は、malloc
関数を呼び出してメモリ領域を確保し、文字列hogefuga
を格納して、それへの先頭ポインタを戻り値とします。
#include <string.h>
char* return_char(void)
{
char*ptr = (char*) malloc(sizeof(char)*8);
strcpy(ptr, "hogefuga");
return ptr;
}
この関数から受け取ったCのポインタからFortranの文字列にするには、まずFortranのポインタへ変換する必要があり、それには長さを取得することが必須なため、Cのstring.h
に含まれる関数strlen
を使用します。
program main
use, intrinsic :: iso_c_binding
implicit none
interface
function c_return_char() bind(c, name='return_char')
import c_ptr
type(c_ptr) :: c_return_char
end function c_return_char
end interface
! Cの標準関数strlen
interface
function strlen(ptr) bind(c)
import c_ptr, c_size_t
type(c_ptr), intent(in), value :: ptr
integer(c_size_t) :: strlen
end function strlen
end interface
! Cの標準関数free
interface
subroutine free(ptr) bind(c)
import c_ptr
type(c_ptr), intent(in), value :: ptr
end subroutine free
end interface
type(c_ptr) :: result
integer :: length
character(:), allocatable :: buf
! 戻り値として文字列へのCポインタを、変数resultに代入する。
result = c_return_char()
! resultの示す文字列の長さを、変数lengthに代入する。
length = strlen(result)
block
! 長さlengthの文字列ポインタfptrを宣言する。
character(len=length, kind=c_char), pointer :: fptr
! 組込み関数c_f_pointerを呼び出して、resultをfptrに変換する。
call c_f_pointer(result, fptr)
! fptrの内容を変数bufにコピーする。
buf = fptr
end block
print *, buf
! Cで確保したメモリ領域を解放するC関数freeを呼び出す。
call free(result)
end program main
なお、fptr
の宣言文でcharacter(len=strlen(result))
と記述する場合には、strlen
関数のinterface
宣言部においてpure
を指定する必要があります。
Fortranから引数で文字列の配列を渡す
文字列の配列をFortranからCに渡します。このとき配列の終端はNULLポインタが格納されると約束します。
#include <stdio.h>
void char_array(const char* const* strArray )
{
int i = 0;
while (*strArray != NULL) {
printf("%s\n", *strArray);
++strArray;
}
return;
}
program main
use, intrinsic :: iso_c_binding
implicit none
interface
subroutine c_char_array(days) bind(c, name="char_array")
import c_ptr
implicit none
type(c_ptr), intent(in) :: days ! 参照渡し
end subroutine c_char_array
end interface
character(16) :: days_of_week(7)
integer :: i, siz, max_len
days_of_week(1) = "Sunday"
days_of_week(2) = "Monday"
days_of_week(3) = "Tuesday"
days_of_week(4) = "Wednesday"
days_of_week(5) = "Thursday"
days_of_week(6) = "Friday"
days_of_week(7) = "Saturday"
! 配列の大きさを変数sizに代入する。
siz = size(days_of_week)
! 文字列の最大長を変数max_lenに格納する。
max_len = 0
do i = 1, siz
max_len = max(max_len, len_trim(days_of_week(i)))
end do
block
! +1はCのNULL文字を末尾に付け加えるため。
character(len=max_len+1, kind=c_char), allocatable, target :: c_dow(:)
! c_loc関数の引数にわたすのでtarget属性が必要。
type(c_ptr), allocatable, target :: ptr(:)
! Cに渡す文字列の配列を準備する。
allocate(c_dow(siz))
do i = 1, siz
! 各要素の末尾にnull文字を付け加える。
c_dow(i) = trim(days_of_week(i))//c_null_char
end do
! Cポインタの配列を用意する。
allocate(ptr(siz+1))
do i = 1, siz
ptr(i) = c_loc(c_dow(i))
end do
! 約束通り、末尾はNULLポインタとする。
ptr(siz+1) = c_null_ptr
! Cの関数を呼び出す。。
call c_char_array(ptr) ! ptrは参照渡し
end block
end program main
配列の大きさを表す整数を引数で渡す場合は、配列終端をNULLポインタにする必要はありません(C側でNULLを終端条件にする必要がない)。
Cから戻り値で文字列の配列を受け取る
事前に配列のサイズと要素の文字列の長さが既知であるか、もしくは未知の場合は複数回呼び出して配列サイズとFortranの文字列ポインタの長さを確定させる必要があります。
配列サイズが既知かつ要素の長さを引数で渡せる場合
配列のサイズが既知で、文字列の各要素の長さを引数で返してくれる場合は次のようになります。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const char* const* return_char_array(int**lengths)
{
const char *days_of_week[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
const char**result = malloc(sizeof(days_of_week));
if (result == NULL) {
fprintf(stderr, "Memory allocation failed.\n");
exit(EXIT_FAILURE);
}
memcpy(result, days_of_week, sizeof(days_of_week));
int i = 0;
for (i = 0; result[i]!=NULL; i++){
(*lengths)[i] = getStrLen(result[i]);
}
return result;
}
int getStrLen(const char*str) {
return (str != NULL) ? strlen(str) : 0;
}
program main
use, intrinsic :: iso_c_binding
implicit none
interface
function c_return_char_array (length) bind(c, name='return_char_array') result(res)
import c_ptr, c_int
type(c_ptr) :: res
integer(c_int) :: length(:)
end function c_return_char_array
end interface
! Cの標準関数free
interface
subroutine free(ptr) bind(c)
import c_ptr
type(c_ptr), intent(in), value :: ptr
end subroutine free
end interface
type(c_ptr) :: result
integer, parameter :: siz = 7 ! 配列サイズが既知
integer :: i
integer :: lengths(siz)
! 引数で文字列長さを格納した配列を受け取る
result = c_return_char_array(length)
block
type(c_ptr), dimension(:), pointer :: ptr_array
character(:), pointer :: fptr
call c_f_pointer(result, ptr_array, shape=[siz])
do i = 1, siz
fptr => c_f_string_pointer(ptr_array(i), lengths(i))
print *, fptr
end do
end block
call free(result)
contains
function c_f_string_pointer (cptr, length) result(res)
use, intrinsic :: iso_c_binding
implicit none
type(c_ptr), intent(in) :: cptr
integer(c_int), intent(in) :: length
character(len=length, kind=c_char), pointer :: res
call c_f_pointer(cptr, res)
end function c_f_string_pointer
end program main
配列要素の長さが未知の場合
配列サイズが未知の場合、一度その関数をC側で呼び出してサイズをFortranに知らせる必要があります。その後、各要素の長さを引数で渡すようにラップするC関数を書いて、配列要素の文字列長をFortran側に知らせてやる必要があります。
これは、c_f_pointer
を呼び出す段階で配列サイズを、c_f_string_pointer
の段階で配列要素の長さを知っていなければならないことに起因します。
#include <stdio.h>
const char *days_of_week[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
int getStrLen(const char*str) {
return (str != NULL) ? strlen(str) : 0;
}
const char* const* return_char_array_global()
{
return days_of_week;
}
const char* const* wrap1_return_char_array_global(int**size)
{
char**ptr = return_char_array_global();
int i;
for (i = 0; ptr[i] != NULL; i++){
}
*size = i;
return ptr;
}
const char* const* wrap2_return_char_array_global(int**lengths)
{
char**ptr = return_char_array_global();
int i;
for (i = 0; ptr[i] != NULL; i++) {
(*lengths)[i] = getStrLen(ptr[i]);
}
return ptr;
}
program main
use, intrinsic :: iso_c_binding
implicit none
interface
function wrap1_return_char_array_global(siz) bind(c) result(res)
import c_ptr, c_int
integer(c_int), intent(out) :: siz
type(c_ptr) :: res
end function wrap1_return_char_array_global
end interface
interface
function wrap2_return_char_array_global(lengths) bind(c) result(res)
import c_ptr, c_int
integer(c_int), intent(out) :: lengths(:)
type(c_ptr) :: res
end function wrap2_return_char_array_global
end interface
type(c_ptr) :: result
integer :: siz, i
! 引数で配列サイズを得る。
result = wrap1_return_char_array_global(siz)
block
integer :: lengths(siz)
! 引数で配列要素の長さを得る。
result = wrap2_return_char_array_global(lengths)
block
type(c_ptr), dimension(:), pointer :: ptr_array
character(:), pointer :: fptr
!
call c_f_pointer(result, ptr_array, shape=[siz])
do i = 1, siz
! 戻り値の文字列ポインタをポインタ変数fptrにポインタ代入する。
fptr => c_f_string_pointer(ptr_array(i), lengths(i))
print *, fptr
end do
end block
end block
contains
! 文字列ポインタを返す関数
function c_f_string_pointer (cptr, length) result(res)
use, intrinsic :: iso_c_binding
implicit none
type(c_ptr), intent(in) :: cptr
integer(c_int), intent(in) :: length
! 引数で渡された文字列長のポインタを宣言する
character(len=length, kind=c_char), pointer :: res
call c_f_pointer(cptr, res)
end function c_f_string_pointer
end program main
なお、サイズと要素の長さが関数の呼び出しごとに変化する可能性がある場合は、この受け渡し方法をとることができません(そしてその場合の解決策は存在するのか不明です)。
まとめ
FortranとCで文字列をやり取りする方法についてまとめました。
Fortran 2023においてc_f_strpointer
が導入されるとのことで、Cの文字列の扱いが多少楽になりそうなので、最後のようなトリッキーなやり方は不要にならないかなと少し期待しています。
参考文献
- NAG社、Fortran Tip集: C との相互利用可能性の例
- John Reid, 2023, https://wg5-fortran.org/N2201-N2250/N2212.pdf