LoginSignup
7
3

FortranとCで文字列を受け渡すinterface文例集

Last updated at Posted at 2023-12-05

概要

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文を追加します。ここでは文字列引数を長さ1kind=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の文字列の扱いが多少楽になりそうなので、最後のようなトリッキーなやり方は不要にならないかなと少し期待しています。

参考文献

7
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3