Crear una interfaz FORTRAN para una función C que devuelve un carácter *

Me he retrasado en esto durante una semana, y he buscado foro tras foro para obtener una explicación clara de cómo enviar un char * de C a FORTRAN. Para hacer que el asunto sea más frustrante, enviar un argumento de char * de FORTRAN a C fue directo …

Enviar un argumento char * de FORTRAN a C (esto funciona bien):

// The C header declaration (using __cdecl in a def file): extern "C" double GetLoggingValue(char* name); 

Y de FORTRAN:

 ! The FORTRAN interface: INTERFACE REAL(8) FUNCTION GetLoggingValue [C, ALIAS: '_GetLoggingValue'] (name) USE ISO_C_BINDING CHARACTER(LEN=1, KIND=C_CHAR), DIMENSION(*), INTENT(IN) :: name END FUNCTION GetLoggingValue END INTERFACE ! Calling the function: GetLoggingValue(user_name) 

Cuando trato de usar una lógica análoga para devolver un carácter * desde C, aparece un problema tras otro. Un bash que sentí que debería funcionar es:

 // The C declaration header (using __cdecl in a def file): extern "C" const char* GetLastErrorMessage(); 

Y la interfaz FORTRAN:

 INTERFACE FUNCTION GetLastErrorMessage [C, ALIAS: '_GetLastErrorMessage'] () USE ISO_C_BINDING CHARACTER(LEN=1, KIND=C_CHAR), DIMENSION(255), :: GetLastErrorMessage END FUNCTION GetLastErrorMessage END INTERFACE 

(No puedo usar literalmente la DIMENSIÓN (*), así que he pasado de tamaño a 255).

Esto debería devolver un puntero a una matriz de 255 caracteres estilo C, pero si lo hace, no he podido convertir esto en una cadena significativa. En la práctica, devuelve un conjunto aleatorio de caracteres, desde Wingdings hasta el personaje ‘campana’ …

También intenté regresar:

  • Un puntero al CARÁCTER (LEN = 255, KIND = C_CHAR).
  • Literalmente CARÁCTER (LEN = 255, KIND = C_CHAR).
  • Un INTEGER (C_SIZE_T), y trató de convertirlo en un puntero a una matriz de cadenas.
  • UN PERSONAJE.
  • etc.

Si alguien puede darme un ejemplo de cómo hacer esto, estaría muy agradecido …

Atentamente,

Micro

Las cadenas de longitud dinámica siempre son un poco complicadas con la interacción C. Una posible solución es usar punteros.

Primero, un caso simple, donde debe entregar una cadena terminada de carácter nulo a una función C. Si realmente solo pasa la cuerda, debe asegurarse de finalizarla con el c_null_char, por lo tanto, esta dirección es bastante directa. Aquí hay ejemplos de una interfaz LuaFortran :

 subroutine flu_getfield(L, index, k) type(flu_State) :: L integer :: index character(len=*) :: k integer(kind=c_int) :: c_index character(len=len_trim(k)+1) :: c_k c_k = trim(k) // c_null_char c_index = index call lua_getfield(L%state, c_index, c_k) end subroutine flu_getfield 

Y la interfaz de lua_getfield se ve así:

 subroutine lua_getfield(L, index, k) bind(c, name="lua_getfield") use, intrinsic :: iso_c_binding type(c_ptr), value :: L integer(kind=c_int), value :: index character(kind=c_char), dimension(*) :: k end subroutine lua_getfield 

Y la interfaz de C-Code es:

 void lua_getfield (lua_State *L, int idx, const char *k) 

Ahora el caso un poco más complejo, donde tenemos que lidiar con una cadena devuelta de C con una longitud dinámica. La solución más portátil que encontré hasta ahora es el uso de punteros. Aquí hay un ejemplo con un puntero, donde la cadena está dada por la C-Rutina (también de la biblioteca de Aotus mencionada anteriormente):

 function flu_tolstring(L, index, len) result(string) type(flu_State) :: L integer :: index integer :: len character,pointer,dimension(:) :: string integer :: string_shape(1) integer(kind=c_int) :: c_index integer(kind=c_size_t) :: c_len type(c_ptr) :: c_string c_index = index c_string = lua_tolstring(L%state, c_index, c_len) len = int(c_len,kind=kind(len)) string_shape(1) = len call c_f_pointer(c_string, string, string_shape) end function flu_tolstring 

donde lua_tolstring tiene la siguiente interfaz :

 function lua_tolstring(L, index, len) bind(c, name="lua_tolstring") use, intrinsic :: iso_c_binding type(c_ptr), value :: L integer(kind=c_int), value :: index integer(kind=c_size_t) :: len type(c_ptr) :: lua_tolstring end function lua_tolstring 

Finalmente, aquí hay un bash de aclarar cómo un c_ptr puede interpretarse como una cadena de caracteres Fortran: Supongamos que tiene un c_ptr apuntando a la cadena:

 type(c_ptr) :: a_c_string 

Y la longitud del mismo viene dada por una variable len con el siguiente tipo:

 integer(kind=c_size_t) :: stringlen 

Desea obtener esta cadena en un puntero a una cadena de caracteres en Fortran:

 character,pointer,dimension(:) :: string 

Entonces haces el mapeo:

 call c_f_pointer(a_c_string, string, [ stringlen ]) 

Mi agradecimiento a heraldkl por darme la solución a este problema tan frustrante. Estoy publicando lo que finalmente implementé, los roles de la conversión del puntero a la interfaz, lo que significa que la aplicación final puede llamar a la función C sin tener que conocer la conversión del puntero:

La función C:

 // The C declaration header (using __cdecl in a def file): extern "C" const char* GetLastErrorMessage(); 

El módulo de interfaz FORTRAN:

 MODULE mINTERFACES USE ISO_C_BINDING INTERFACE FUNCTION GetLastErrorMessagePtr [C, ALIAS: '_GetLastErrorMessage'] () USE ISO_C_BINDING TYPE(C_PTR) :: GetLastErrorMessagePtr END FUNCTION GetLastErrorMessagePtr END INTERFACE CONTAINS ! this must be after all INTERFACE definitions FUNCTION GetLastErrorMessage() USE ISO_C_BINDING CHARACTER*255 :: GetLastErrorMessage CHARACTER, POINTER, DIMENSION(:) :: last_message_array CHARACTER*255 last_message INTEGER message_length CALL C_F_POINTER(GetLastErrorMessagePtr(), last_message_array, [ 255 ]) DO 10 i=1, 255 last_message(i:i+1) = last_message_array(i) 10 CONTINUE message_length = LEN_TRIM(last_message(1:INDEX(last_message, CHAR(0)))) GetLastErrorMessage = last_message(1:message_length-1) END FUNCTION GetLastErrorMessage 

Y para llamar a esta función desde un progtwig FORTRAN:

 USE MINTERFACES PRINT *, "--> GetLastErrorMessage: '", TRIM(GetLastErrorMessage()), "'" 

Mi agradecimiento nuevamente a heraldkl por proporcionar esta solución. No tenía ni idea de cómo hacerlo sin su aporte.

Este hilo es un poco viejo, pero como tuve un problema similar (y probablemente otros lo hagan), publico una respuesta de todos modos.

Los códigos publicados anteriormente causarán un error de segmentación si, por alguna razón, la cadena C es nula. Además, no es necesario devolver una cadena de 255 caracteres (que probablemente será necesario recortar antes de su uso), ya que Fortran 2003/2008 es compatible con funciones que devuelven entidades asignables. Utilizando toda la información publicada anteriormente, terminé con la siguiente función, que obtiene una cadena C (puntero) y devuelve la cadena Fortran correspondiente; Si la cadena C es nula, devuelve “NULO”, de forma similar a C “(nulo)” impreso en casos similares:

 function C_to_F_string(c_string_pointer) result(f_string) use, intrinsic :: iso_c_binding, only: c_ptr,c_f_pointer,c_char,c_null_char type(c_ptr), intent(in) :: c_string_pointer character(len=:), allocatable :: f_string character(kind=c_char), dimension(:), pointer :: char_array_pointer => null() character(len=255) :: aux_string integer :: i,length call c_f_pointer(c_string_pointer,char_array_pointer,[255]) if (.not.associated(char_array_pointer)) then allocate(character(len=4)::f_string); f_string="NULL"; return end if aux_string=" " do i=1,255 if (char_array_pointer(i)==c_null_char) then length=i-1; exit end if aux_string(i:i)=char_array_pointer(i) end do allocate(character(len=length)::f_string) f_string=aux_string(1:length) end function C_to_F_string 

Siempre lucho con estas funciones de interoperabilidad. Creo que su interfaz debería declarar

 CHARACTER(KIND=C_CHAR),DIMENSION(*) :: getlasterrormessage 

y que, cuando llame a la función, pase una variable de caracteres Fortran correspondiente con una longitud igual o mayor que la longitud de la matriz de C caracteres que espera devolver.

Dado que parece tener Intel Fortran, consulte las muestras de código proporcionadas, dan un ejemplo completo de esto.

Supongo que sabes que lo que has publicado no es sintácticamente correcto Fortran.

También he luchado con llamar a una rutina C que devuelve una cadena y las respuestas anteriores han sido muy útiles, pero como sé casi nada de C y las respuestas son un poco confusas, solo quería aportar mi solución que utiliza un puntero C, lo hice no logra hacer uso de ninguna de las otras propuestas anteriores. El progtwig C al que llamo abre una ventana separada para buscar un nombre de archivo.

 program test use iso_c_binding implicit none ! AC function that returns a string need a pointer to the array of single char type (c_ptr) :: C_String_ptr ! This is the Fortran equivalent to a string of single char character (len=1, kind=c_char), dimension(:), pointer :: filchar=>null() ! Interface to a C routine which opens a window to browse for a file to open interface function tinyopen(typ) bind(c, name="tinyopen") use iso_c_binding implicit none integer(c_int), value :: typ type (C_Ptr) :: tinyopen end function tinyopen end interface character (len=256) :: filename integer typ,jj typ=1 C_String_ptr = tinyopen(typ) ! convert C pointer to Fortran pointer call c_f_pointer(C_String_ptr,filchar,[256]) filename=' ' if(.not.associated(filchar)) then ! if no characters give error message write(*,*)'No file name' else ! convert the array of single characters to a Fortran character jj=1 do while(filchar(jj).ne.c_null_char) filename(jj:jj)=filchar(jj) jj=jj+1 enddo endif write(*,*)'Text is: ',trim(filename) end program test 

Esperamos que este ejemplo lo haga más fácil para el próximo con el mismo problema.

En Fortran, el elemento debe declararse como “carácter (tipo = c_char, len = 1), dimensión (255)” en lugar de len = 255. Esto creará una matriz de caracteres de longitud uno, que es lo que necesita en el lado C. Lo que puede ser confuso es que hay una excepción que permite a Fortran hacer coincidir cadenas con matrices unidimensionales.

¿Quiere decir que quiere llamar a un procedimiento de Fortran desde C? Vea este ejemplo: Llamar a una subrutina FORTRAN desde C.

EDITAR: Tanto ifort como gfortran dicen que las matrices no están permitidas ya que la función vuelve en este contexto. Lo cual hace que devolver cadenas como argumentos de función de C a Fortran sea más difícil que usar una cadena como argumento (ejemplo en el enlace de arriba) … debes usar puntero y luego el c_f_pointer Fortran intrínseco para convertir de la secuencia C a una cadena Fortran , según lo explicado por haraldkl. Aquí hay otro ejemplo de código:

 program test_c_func use iso_c_binding implicit none type (C_PTR) :: C_String_ptr character (len=1, kind=c_char), dimension (:), pointer :: ErrChars => null () character (len=255) :: ErrString integer :: i INTERFACE FUNCTION GetLastErrorMessage () bind (C, name="GetLastErrorMessage" ) USE ISO_C_BINDING type (C_PTR) :: GetLastErrorMessage END FUNCTION GetLastErrorMessage END INTERFACE C_String_ptr = GetLastErrorMessage () call c_f_pointer ( C_String_ptr, ErrChars, [255] ) ErrString = " " xfer_string: do i=1, 255 if ( ErrChars (i) == c_null_char) exit xfer_string ErrString (i:i) = ErrChars (i) end do xfer_string write (*, '( "Fortran: <", A, ">" )' ) trim (ErrString) end program test_c_func 

Si conoce la longitud de la cadena, entonces la respuesta anterior de Pap se puede simplificar enormemente:

 function stringc2f(n, cstr) result(fstr) integer, intent(in) :: n type(c_ptr), intent(in) :: cstr character(:), allocatable :: fstr character(n, kind=c_char), pointer :: fptr call c_f_pointer(cstr, fptr) fstr = fptr end function 

La función anterior acepta un puntero C con la cadena y la longitud de la cadena, y devuelve una copia como una cadena Fortran.