c++ - que - matriz de caracteres en c
¿Cómo pasar arreglos de cadenas de C y Fortran a Fortran? (3)
Estoy tratando de pasar una serie de cadenas de C a una subrutina Fortran, así como de Fortran a esa misma subrutina Fortran. He logrado pasar cadenas simples (es decir, matrices de caracteres 1D) con éxito desde C y Fortran. Sin embargo, tengo problemas con los arreglos de cadenas. Estoy usando el enlace ISO C en el lado de Fortran, e idealmente me gustaría que esto sea lo más transparente posible en el lado de la llamada.
He leído algunas preguntas y respuestas relacionadas. Algunos, (es decir, esto y esto ) son simplemente "Use ISO C" sin más detalles, lo que no ayuda mucho. Esta respuesta fue muy útil (respuesta similar a una pregunta diferente ), pero solo funciona para cadenas simples, donde parece que el c_null_char se reconoce en la cadena Fortran única. No puedo imaginar qué hacer para el caso de matriz sin tener dos rutinas separadas.
Lo que tengo actualmente es una rutina C que quiero pasar la matriz de cadenas ( string
) de:
#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;
}
Y, una rutina Fortran similar:
program main
implicit none
call print_hi_array( (/"asdf", "ghji", "zxcv"/) )
end program
Hasta ahora, esto es lo que tengo para el receptor:
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
Esto funciona para la función C; Obtengo esta salida:
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
Sin embargo, cuando se hace a través de Fortran me sale sin sentido:
asdfghjizxcv@O,B�@(P,B�]B]6(P,B�@ .......
Parece que no hay c_null_char
en este enfoque.
Entonces, ¿cómo puedo escribir una subrutina Fortran para tomar arreglos de cadenas tanto de C como de Fortran?
Fortran usa espacios para llenar el resto de la cadena si se declara más larga que su texto almacenado. No está delimitado por cero, la longitud declarada se almacena en una variable oculta. No contiene c null char y, por lo tanto, está leyendo basura (desbordamiento de búfer). Lo que Fortran debería imprimir cuando tlit imprime una cadena con / 000 no está definido por la norma y depende de la implementación.
En particular, también está transfiriendo una matriz de caracteres (4) con la dimensión 3 a una subrutina que espera mucha más información (255 caracteres, aunque no estoy seguro del orden de los índices). Solo se pasan los punteros, así que creo que no se puede verificar.
Es posible definir la longitud de las cadenas en el constructor de la matriz de esta manera:
[character(255) :: "a","ab","abc"]
Un enfoque que se me ocurrió es modificar la rutina llamada Fortran para usar también el enlace 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
Veo en realidad dos maneras de hacer eso. O bien, escribes un ciclo en C y pasas las cuerdas una por una a Fortran, como ya lo hiciste antes. Alternativamente, si quiere pasar la matriz completa y quiere manejar las matrices Fortran y C con la misma rutina, tendrá que hacer una copia apropiada de su matriz C-string. Debajo de un ejemplo que funciona, pero no demasiado probado:
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;
}
Tenga en cuenta que también paso el número de cadenas, para que el ejemplo pueda manejar matrices con varios tamaños. (Sin embargo, se supone que la longitud de las cadenas es de 255 caracteres). En el tamaño de Fortran, uno necesitaría una rutina para convertirlo en cadenas Fortran. Una posible visualización podría ser:
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
Tenga en cuenta que la matriz que pasa desde C debe ser contigua para que esto funcione y supongo que el carácter (kind = c_char) es compatible con el tipo de carácter fortran, que normalmente debería ser.