todas - Crear una interfaz FORTRAN para una función C que devuelve un carácter*
todas las funciones de c++ (6)
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 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 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
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.
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 intento 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 ])
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
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 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 de arriba) ... tienes que usar un 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
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 programa 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.
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.