Я пытаюсь передать массив строк из 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
в этом подходе.
Итак, как мне написать подпрограмму на Фортране, которая будет принимать массивы строк из Си и Фортрана?
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 cstrlenend 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