Creando una interfaz FORTRAN para una función C que devuelve un char*

12 minutos de lectura

avatar de usuario
mike sadler

He estado detenido en esto durante aproximadamente 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 el asunto más frustrante, enviar un argumento char* desde FORTRAN a C fue sencillo…

Enviando un argumento char* desde FORTRAN a C (esto funciona bien):

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

Y desde 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 * de C, obtengo un problema tras otro. Un intento 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 ido sobredimensionado a 255.) Esta deberían

devolver un puntero a una matriz de 255 caracteres de 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 carácter ‘campana’…

  • También he intentado volver:
  • Un puntero a CHARACTER(LEN=255, KIND=C_CHAR).
  • Literalmente CARÁCTER (LEN=255, TIPO=C_CHAR).
  • A INTEGER(C_SIZE_T), y traté de convertirlo en un puntero a una matriz de cadenas.
  • UN PERSONAJE.

etc

si alguien me puede dar un ejemplo de como hacerlo se lo agradeceria mucho…

Atentamente,

  • Miguel

    ¿Qué cadenas de herramientas estás usando?

    – Michael Burr

  • 2 de abril de 2012 a las 8:07

    Estoy usando el compilador VC ++, pero compilando la interfaz en C puro. FORTAN es Visual Fortran 2011, que creo que es FORTRAN 90. Sin embargo, esto es para una API publicada, por lo que debe poder llamarse desde tantos sabores como sea posible. ..

    –Mike Sadler


2 de abril de 2012 a las 8:19
avatar de usuario

haraldkl

Las cadenas de longitud dinámica siempre son un poco complicadas con la interacción C. Una posible solución es utilizar punteros. Primero, un caso simple, en el que debe entregar una cadena terminada en un carácter nulo a una función C. Si realmente pasa la cadena solo, debe asegurarse de finalizarla con c_null_char, por lo que esta dirección es bastante sencilla. Aquí hay ejemplos de unInterfaz 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 el interfaz

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

de lua_getfield se parece a:

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

Y la interfaz de C-Code es:

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

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 usar punteros. Aquí hay un ejemplo con un puntero, donde la C-Routine proporciona la cadena (también de la biblioteca Aotus mencionada anteriormente): donde lua_tolstring tiene lo siguienteinterfaz

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

:

type(c_ptr) :: a_c_string

Finalmente, aquí hay un intento de aclarar cómo se puede interpretar un c_ptr como una cadena de caracteres Fortran: suponga que tiene un c_ptr que apunta a la cadena:

integer(kind=c_size_t) :: stringlen

Y la longitud de la misma viene dada por una variable len con el siguiente tipo:

character,pointer,dimension(:) :: string

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

call c_f_pointer(a_c_string, string, [ stringlen ])

  • Entonces haces el mapeo:

    ¡Gracias, heraldkl! El ejemplo anterior es para interactuar con una función C nula: ¿funcionaría este enfoque cuando el tipo de retorno de la función C es un char*? Mi paradigma operativo general es que mis funciones API devuelvan el resultado, en lugar de pasarlas a través de subrutinas vacías, y esto funciona bien cuando se usan todos los demás tipos de datos, EXCEPTO caracteres 🙁

    –Mike Sadler

  • 2 de abril de 2012 a las 9:37

    Supongo que el enfoque anterior también funcionaría con funciones no nulas. No veo una razón por la que esto deba estar influenciado por el valor de retorno.

    – Haraldkl

  • 2 abr 2012 a las 10:10

    Ciertamente estoy feliz de usar punteros; de hecho, ese fue mi primer enfoque. Me temo que tengo problemas para seguir los fragmentos que proporcionó anteriormente. ¿Es todo necesario para convertir un puntero C en una matriz de caracteres const en una cadena FORTRAN? Para decirlo de otra manera, si tengo un TIPO (C_PTR) y una longitud de cadena, ¿cuál es el mínimo que necesito para convertir esto en una cadena FORTRAN? Siento ser obtuso…

    –Mike Sadler

  • 2 abr 2012 a las 10:51

    intenté agregar algún ejemplo para la transferencia a mi respuesta ahora;)

    – Haraldkl

  • 2 abr 2012 a las 14:21

    ¡FUNCIONA! Muchas, muchas gracias heraldkl – Estuve cerca de pensar que era imposible. Gracias a todos los muchachos que me ayudaron y comentaron; me ayudó a continuar.

    –Mike Sadler

02/04/2012 a las 14:30
avatar de usuario

mike sadler

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

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

La función C:

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

El módulo de interfaz FORTRAN:

USE MINTERFACES

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

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

  • Mi agradecimiento nuevamente a heraldkl por proporcionar esta solución; no habría tenido idea de cómo hacer esto sin su aporte.

    No hay problema, me alegro de que te haya ayudado 😉

    – Haraldkl

3 abr 2012 a las 18:53

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

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

  • Los códigos publicados anteriormente provocarán una falla 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 deba recortarse antes de usarse), ya que Fortran 2003/2008 admite funciones que devuelven entidades asignables. Usando 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 “NULL”, de manera similar a “(null)” de C impreso en casos similares:

    Publiqué una pequeña simplificación de su subrutina a continuación.

    – Ondřej Čertík

15 de noviembre de 2016 a las 20:09

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

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

y que, cuando llama a la función, pasa una variable de carácter Fortran correspondiente con una longitud igual o mayor que la longitud de la matriz de caracteres C que espera devolver.

Dado que parece tener Intel Fortran, mire los ejemplos de código proporcionados, le brindan un ejemplo completo para esto.

¿Supongo que sabe que lo que ha publicado no es Fortran sintácticamente correcto?

program test
  use iso_c_binding
  implicit none
! A C 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

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

Esperemos que este ejemplo lo haga más fácil para el próximo con el mismo problema.
avatar de usuario

Comunidad

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 resultar confuso es que existe una excepción que permite a Fortran hacer coincidir cadenas con arreglos unidimensionales.

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

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

EDITAR: tanto ifort como gfortran dicen que las matrices no están permitidas como función devuelve en este contexto. Lo que 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 anterior) … debe usar el puntero y luego el c_f_pointer Fortran intrínseco para convertir de la cadena C a una cadena Fortran , como lo explica haraldkl. Aquí hay otro ejemplo de código:

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

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

  • 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.nNo estoy seguro de que esto sea Fortran moderno, mientras que mi respuesta no lo es; de hecho, ambos códigos son Fortran 2003. De todos modos, su código es más simple PERO requiere pasar la longitud de la cadena (

    ), como argumento. Por supuesto, conocer la longitud de la cadena de antemano hace que el código sea mucho más pequeño, pero también hace que la función sea menos útil. En la mayoría de los casos, simplemente no sabe cuánto dura la cadena C.

    – Papanicolaou

  • 15 de junio de 2017 a las 11:18

    @Papi, tienes razón. He aclarado mi respuesta para reflejar esto. De hecho, ambos son F2003. Usé la asignación automática del LHS, pero eso también es F2003. Para mi aplicación, tengo acceso a los códigos C y Fortran, por lo que no es un problema pasar la longitud de la cadena C a la interfaz, lo que simplifica enormemente las cosas.

    – Ondřej Čertík

¿Ha sido útil esta solución?