Как передать массивы строк из C и Fortran в Fortran?

Я пытаюсь передать массив строк из C в подпрограмму Fortran, а также из Fortran в ту же подпрограмму Fortran. Мне удалось успешно передать одиночные строки (то есть одномерные массивы символов) из C и Fortran. Тем не менее, у меня проблемы с массивами строк. Я использую привязку ISO C на стороне Фортрана, и в идеале я хотел бы, чтобы это было как можно более гладко на стороне вызова.

Я прочитал некоторые связанные вопросы и ответы. Некоторые (то есть это и это) просто "используют ISO C" без дополнительных подробностей, что не очень помогает. Этот ответ был очень полезен (аналогичен ответу на другой вопрос), но он работает только для отдельных строк, где кажется, что c_null_char распознается в одной строке Fortran. Я не могу понять, что делать с массивом, не имея двух отдельных подпрограмм.

В настоящее время у меня есть подпрограмма C, которую я хочу передать массив строк (string) от:

#include <iostream>

extern "C" void print_hi_array(char input_string[][255]);

using namespace std;

int main() {

  char string[3][255] = {"asdf","ghji","zxcv"};   
  print_hi_array(string);

  return 0;
}

И, аналогичная рутина Фортрана:

program main
  implicit none
  call print_hi_array( (/"asdf", "ghji", "zxcv"/) )
end program

Пока что это то, что я имею для принимающей стороны:

subroutine print_hi_array(input_string) bind(C)
  use iso_c_binding, only: C_CHAR, c_null_char

  implicit none

  character (kind=c_char, len=1), dimension (3,255), intent (in) :: input_string
  character (len=255), dimension (3) :: regular_string
  character (len=255) :: dummy_string
  integer :: i,j,k

  write (*,*) input_string

  do j = 1 , 3
    dummy_string(:) = c_null_char
    k = 1
    do i = 1 + (j-1)*255, j*255,1
      if (input_string(i) .ne.  c_null_char) then
        write (*,*) "i ",i,j, input_string(i)
        dummy_string(k:k) = input_string(i)
      endif
    k = k +1
    enddo
    regular_string(j) = dummy_string
  enddo

  write (*,*) regular_string

end subroutine print_hi_array

Это работает для функции C; Я получаю этот вывод:

 asdfghjizxcv
 j=           1
 i            1           1 a
 i            2           1 s
 i            3           1 d
 i            4           1 f
 j=           2
 i          256           2 g
 i          257           2 h
 i          258           2 j
 i          259           2 i
 j=           3
 i          511           3 z
 i          512           3 x
 i          513           3 c
 i          514           3 v
 asdf   ghji   zxcv   

Однако, когда это делается через Фортран, я получаю ерунду:

asdfghjizxcv@O,B�@(P,B�]B]6(P,B�@ .......

Похоже нет c_null_char в этом подходе.

Итак, как мне написать подпрограмму на Фортране, которая будет принимать массивы строк из Си и Фортрана?

3 ответа

Fortran использует пробелы для заполнения оставшейся части строки, если она объявлена ​​длиннее, чем ее сохраненный текст. Он не разделен нулями, заявленная длина сохраняется в скрытой переменной. Он не содержит c null char и, следовательно, вы читаете какую-то фигню (переполнение буфера). То, что Fortran должен печатать, когда tlit печатает строку с \000, не определено стандартом и зависит от реализации.

В частности, вы также передаете массив символьных данных (4) с измерением 3 подпрограмме, которая ожидает гораздо больше данных (255 символов, хотя я не уверен насчет порядка индекса). Передаются только указатели, поэтому я думаю, что это не может быть проверено.

Можно определить длину строк в конструкторе массива следующим образом:

[character(255) :: "a","ab","abc"]

Я вижу на самом деле два способа сделать это. Либо вы пишете цикл в C и передаете строки одну за другой в Fortran, как вы уже делали это раньше. В качестве альтернативы, если вы хотите передать весь массив и обрабатывать массивы Fortran и C одной и той же подпрограммой, вам придется сделать соответствующую копию массива C-string. Ниже рабочий, но не слишком проверенный пример:

extern "C" void print_array_c(int nstring, char input_string[][255]);

using namespace std;

int main() {

  char string[3][255] = {"asdf","ghji","zxcv"};    
  print_array_c(3, string);

  return 0;
}

Обратите внимание, что я также передаю количество строк, чтобы в примере можно было обрабатывать массивы разных размеров. (Предполагается, что длина строк составляет 255 символов.) Для размера Фортрана понадобится подпрограмма для его преобразования в строки Фортрана. Одной из возможных визуализаций может быть:

module arrayprint_module
  use, intrinsic :: iso_c_binding
  implicit none

  integer, parameter :: STRLEN = 255

contains

  !> The printing routine, works with Fortran character arrays only.
  subroutine print_array(strings)
    character(len=STRLEN), intent(in) :: strings(:)

    integer :: ii

    do ii = 1, size(strings)
      write(*,*) ii, strings(ii)
    end do

  end subroutine print_array


  !> Converts C string array to Fortran string array and invokes print_array.
  subroutine print_array_c(nstring, cptr) bind(C)
    integer(c_int), value :: nstring
    type(c_ptr), intent(in), value :: cptr

    character(kind=c_char), pointer :: fptr(:,:)
    character(STRLEN), allocatable :: fstrings(:)
    integer :: ii, lenstr

    call c_f_pointer(cptr, fptr, [ STRLEN, nstring ])
    allocate(fstrings(nstring))
    do ii = 1, nstring
      lenstr = cstrlen(fptr(:,ii))
      fstrings(ii) = transfer(fptr(1:lenstr,ii), fstrings(ii))
    end do
    call print_array(fstrings)

  end subroutine print_array_c


  !> Calculates the length of a C string.
  function cstrlen(carray) result(res)
    character(kind=c_char), intent(in) :: carray(:)
    integer :: res

    integer :: ii

    do ii = 1, size(carray)
      if (carray(ii) == c_null_char) then
        res = ii - 1
        return
      end if
    end do
    res = ii

  end function cstrlen


end module arrayprint_module

Обратите внимание, что массив, который вы передаете из C, должен быть корректным, чтобы это работало, и я предположил, что символ (kind=c_char) совместим с типом символов fortran, что обычно и должно быть.

Один из подходов, который я предложил, состоит в том, чтобы изменить вызывающую подпрограмму Fortran, чтобы также использовать привязку ISO C:

program main

  use iso_c_binding, only: C_CHAR
  implicit none
  character (kind=c_char, len=255), dimension (3) :: input_string

  input_string = (/ "asdf", "ghji", "zxcv" /)

  call print_hi_array(input_string)

end program
Другие вопросы по тегам