Cómo acceder a los arreglos de Fortran (asignados dinámicamente) en C

Mi pregunta principal es por qué los arreglos hacen cosas tan extrañas y si hay alguna forma de hacer lo siguiente de manera “limpia”.

Actualmente tengo un progtwig C foo.c interactúa con un progtwig Fortran bar.f90 través de dlopen/dlsym , aproximadamente como en el siguiente código:

foo.c:

 #include  #include  int main() { int i, k = 4; double arr[k]; char * e; void * bar = dlopen("Code/Test/bar.so", RTLD_NOW | RTLD_LOCAL); void (*allocArray)(int*); *(void **)(&allocArray) = dlsym(bar, "__bar_MOD_allocarray"); void (*fillArray)(double*); *(void **)(&fillArray) = dlsym(bar, "__bar_MOD_fillarray"); void (*printArray)(void); *(void **)(&printArray) = dlsym(bar, "__bar_MOD_printarray"); double *a = (double*)dlsym(bar, "__bar_MOD_a"); for(i = 0; i < k; i++) arr[i] = i * 3.14; (*allocArray)(&k); (*fillArray)(arr); (*printArray)(); for(i = 0; i < 4; i++) printf("%f ", a[i]); printf("\n"); return 0; } 

bar.f90:

 module bar integer, parameter :: pa = selected_real_kind(15, 307) real(pa), dimension(:), allocatable :: a integer :: as contains subroutine allocArray(asize) integer, intent(in) :: asize as = asize allocate(a(asize)) return end subroutine subroutine fillArray(values) real(pa), dimension(as), intent(in) :: values a = values return end subroutine subroutine printArray() write(*,*) a return end subroutine end module 

Ejecución de rendimientos principales.

 0.0000000000000000 3.1400000000000001 6.2800000000000002 9.4199999999999999 0.000000 -nan 0.000000 0.000000 

lo que muestra que Fortran asigna la matriz correctamente e incluso almacena correctamente los valores dados, pero ya no se puede acceder a ellos a través de dlsym (trabajar en los resultados de datos en segfaults) También probé esto para arreglos de tamaño fijo: los resultados siguen siendo los mismos.

¿Alguien sabe la razón de este comportamiento? Personalmente, hubiera esperado que las cosas funcionaran de forma bidireccional o, en su defecto, no en absoluto. Este “Fortran acepta matrices C, pero no al revés” me hace preguntarme si hay algún error básico que cometí al acceder a la matriz desde C de esta manera.

La otra pregunta (y aún más importante) es cómo hacer los accesos a la matriz como estos “de la manera correcta”. Actualmente, ni siquiera estoy seguro de si seguir la interfaz “Fortran as .so” es una buena forma. Creo que también sería posible intentar una progtwigción mixta en este caso. No obstante, el problema de las matrices sigue siendo: leí que esto podría solucionarse de alguna manera utilizando el enlace ISO C, pero no pude descubrir cómo (aún no he trabajado mucho con Fortran, especialmente no con el enlace mencionado) , por lo que la ayuda en este tema sería muy apreciada.

Editar:

De acuerdo, leí un poco más el enlace ISO C y encontré un enfoque bastante útil aquí . Usando C_LOC puedo obtener punteros C para mis estructuras Fortran. Desafortunadamente, los punteros a las matrices parecen ser punteros a los punteros y necesitan ser referenciados en el código C antes de que puedan ser tratados como matrices C, o algo así.

Editar:

Logré que mi progtwig funcionara ahora usando la unión de C de la forma en que Vladimir F señaló, al menos en su mayor parte. El archivo C y los archivos Fortran ahora están vinculados entre sí, por lo que puedo evitar la interfaz libdl, al menos para la parte Fortran. Todavía necesito cargar una biblioteca dinámica C, obtener un puntero de función a uno de los símbolos allí y pasar esa como un puntero a la función de Fortran, que más tarde llama a esa función como parte de su cálculo. Como dicha función espera el doble * s [arreglos], no pude pasar mis arreglos Fortran usando C_LOC, extrañamente, ni C_LOC(array) ni C_LOC(array(1)) pasaron los punteros correctos a la función C. Sin embargo, array(1) hizo el truco. Lamentablemente, esta no es la forma “más limpia” de hacer esto. Si alguien me dio una pista sobre cómo hacer esto con la función C_LOC , sería genial. No obstante, acepto la respuesta de Vladimir F, ya que considero que es la solución más segura.

En mi opinión, no es una buena práctica intentar acceder a los datos globales en la biblioteca Fortran. Se puede hacer usando bloques COMUNES, pero son malos y requieren matrices de tamaño estático. En general, la asociación de almacenamiento es una mala cosa.

Nunca acceda a los símbolos del módulo, ya que “__bar_MOD_a” son específicos del comstackdor y no deben utilizarse directamente. Pasa poiters utilizando funciones y subrutinas.

Pase la matriz como un argumento de subrutina. También puede asignar la matriz en C y pasarla a Fortran. Lo que también se puede hacer es obtener un puntero al primer elemento de la matriz. Servirá es el puntero C a la matriz.

Mi solución, por simplicidad sin el .so, es trivial agregarlo:

bar.f90

 module bar use iso_C_binding implicit none integer, parameter :: pa = selected_real_kind(15, 307) real(pa), dimension(:), allocatable,target :: a integer :: as contains subroutine allocArray(asize,ptr) bind(C,name="allocArray") integer, intent(in) :: asize type(c_ptr),intent(out) :: ptr as = asize allocate(a(asize)) ptr = c_loc(a(1)) end subroutine subroutine fillArray(values) bind(C,name="fillArray") real(pa), dimension(as), intent(in) :: values a = values end subroutine subroutine printArray() bind(C,name="printArray") write(*,*) a end subroutine end module 

C Principal

 #include  #include  int main() { int i, k = 4; double arr[k]; char * e; double *a; void allocArray(int*,double**); void fillArray(double*); void allocArray(); for(i = 0; i < k; i++) arr[i] = i * 3.14; allocArray(&k,&a); fillArray(arr); printArray(); for(i = 0; i < 4; i++) printf("%f ", a[i]); printf("\n"); return 0; } 

comstackr y ejecutar:

 gcc -c -g main.c gfortran -c -g -fcheck=all bar.f90 gfortran main.o bar.o ./a.out 0.0000000000000000 3.1400000000000001 6.2800000000000002 9.4199999999999999 0.000000 3.140000 6.280000 9.420000 

Nota: no hay ninguna razón para las devoluciones en sus subrutinas de Fortran, solo ocultan el código.

Muchos comstackdores de Fortran utilizan internamente algo que se denomina descriptores de matriz: estructuras que mantienen la forma de la matriz (es decir, el tamaño y el rango de cada dimensión, así como el puntero a los datos reales). Permite la implementación de cosas como argumentos de matriz de forma supuesta, punteros de matriz y matrices asignables para que funcionen. Lo que está accediendo a través del símbolo __bar_MOD_a es el descriptor de la matriz asignable y no sus datos.

Los descriptores de matrices son específicos del comstackdor y el código que se basa en el formato del descriptor específico no es portátil. Descriptores de ejemplo:

  • GNU Fortran
  • Intel Fortran

Tenga en cuenta que incluso aquellos son específicos de algunas versiones de esos comstackdores. Intel, por ejemplo, afirma que su formato de descriptor actual no es compatible con el formato utilizado en Intel Fortran 7.0.

Si observa ambos descriptores, verá que son más parecidos y que el primer elemento es un puntero a los datos de la matriz. Por lo tanto, podría leer fácilmente los datos usando double ** lugar de double * :

 double **a_descr = (double**)dlsym(bar, "__bar_MOD_a"); ... for(i = 0; i < 4; i++) printf("%f ", (*a_descr)[i]); 

Una vez más, esto no es portátil ya que el formato de esos descriptores podría cambiar en el futuro (aunque dudo que el puntero de datos se mueva a otro lugar que no sea al comienzo del descriptor). Hay un borrador de especificación que intenta unificar todos los formatos de descriptores, pero no está claro cómo y cuándo será adoptado por los diferentes proveedores del comstackdor.

Edición: aquí se explica cómo usar una función de acceso que usa C_LOC() del módulo ISO_C_BINDING para obtener de forma portátil un puntero a la matriz asignable:

Código de Fortran:

 module bar use iso_c_binding ... ! Note that the array should be a pointer target real(pa), dimension(:), allocatable, target :: a ... contains ... function getArrayPtr() result(cptr) type(c_ptr) :: cptr cptr = c_loc(a) end function end module 

Código C:

 ... void * (*getArrayPtr)(void); *(void **)(&getArrayPtr) = dlsym(bar, "__bar_MOD_getarrayptr"); ... double *a = (*getArrayPtr)(); for(i = 0; i < 4; i++) printf("%f ", a[i]); ... 

Resultado:

 $ ./prog.x 0.0000000000000000 3.1400000000000001 6.2800000000000002 9.4199999999999999 0.000000 3.140000 6.280000 9.420000