promedio - problemas resueltos de programación en fortran 95 pdf
Cómo acceder(dinámicamente asignado) matrices Fortran en C (2)
Mi pregunta principal es por qué las matrices hacen cosas tan extrañas y si hay alguna manera de hacer lo siguiente de una manera "limpia".
Actualmente tengo un programa C foo.c
interconectando un programa Fortran bar.f90
través de dlopen/dlsym
, más o menos como en el siguiente código:
foo.c:
#include <dlfcn.h>
#include <stdio.h>
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
que muestra que Fortran asigna la matriz de forma correcta e incluso almacena correctamente los valores dados, pero ya no se puede acceder a ellos a través de dlsym (trabajar con esos datos da como resultado segfaults). También probé esto para arreglos de tamaño fijo, los resultados siguen siendo los mismos.
¿Alguien sabe el motivo de este comportamiento? Personalmente, hubiera esperado que las cosas funcionaran bidireccionalmente o que no funcionaran en absoluto. Este "Fortran acepta cabinas C pero no al revés" me hace preguntarme si hay algún error básico que haya cometido al acceder al conjunto de C de esta manera.
La otra (y aún más importante) pregunta es cómo hacer arreglos de acceso como estos "de la manera correcta". Actualmente, ni siquiera estoy seguro de si seguir la interfaz "Fortran as .so" es una buena manera, creo que también sería posible intentar una programación mixta en este caso. Sin embargo, la cuestión de los arreglos sigue en pie. He leído que esto podría resolverse de alguna manera con el enlace ISO C, pero no pude averiguar cómo, (aún no he trabajado mucho con Fortran, especialmente no con dicho enlace). , así que la ayuda en este tema sería muy apreciada.
Editar:
De acuerdo, entonces leí un poco más sobre el ISO C Binding y encontré un enfoque bastante útil aquí . Utilizando C_LOC
puedo obtener punteros C para mis estructuras Fortran. Desafortunadamente, los punteros a las matrices parecen apuntar a los punteros y deben desreferenciarse en el código C antes de que puedan tratarse como matrices en C, o algo así.
Editar:
Conseguí que mi programa funcionara ahora con C vinculante de la manera que Vladimir F señaló, al menos en su mayor parte. El archivo C y los archivos Fortran ahora están enlazados, así que puedo evitar la interfaz libdl, al menos para la parte Fortran; aún necesito cargar una biblioteca C dinámica, obtener un puntero a uno de los símbolos y pasar eso como un puntero a la función de Fortran, que luego llama a esa función como parte de su cálculo. Como dicha función espera double * s [arrays], no pude lograr pasar mis matrices Fortran usando C_LOC, curiosamente, ni C_LOC(array)
ni C_LOC(array(1))
pasaron los punteros correctos a la función C. array(1)
hizo el truco sin embargo. Tristemente, esta no es la manera "más limpia" de hacer esto. Si alguien tiene una pista para mí sobre cómo hacer esto usando 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.
Muchos compiladores de Fortran usan internamente algo llamado descriptores de matriz: estructuras que mantienen la forma de la matriz (que es 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 asumida, punteros de matriz y matrices asignables para trabajar. 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 matriz son específicos del compilador y el código que se basa en un formato de descriptor específico no es portátil. Descriptores de ejemplo:
Tenga en cuenta que incluso aquellos son específicos de algunas versiones de esos compiladores. 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 similares y que el primer elemento es un puntero a los datos de la matriz. Por lo tanto, podría leer fácilmente los datos utilizando el double **
lugar del 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 principio del descriptor). Existe 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 de compiladores.
Editar: Aquí se muestra cómo usar una función de acceso que utiliza C_LOC()
desde el módulo ISO_C_BINDING para obtener de forma portátil un puntero a la matriz asignable:
Código 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
En mi opinión, no es una buena práctica intentar acceder a datos globales en la biblioteca Fortran. Se puede hacer utilizando bloques COMUNES, pero son malvados y requieren matrices de tamaño estático. En general, la asociación de almacenamiento es algo malo.
Nunca acceda a los símbolos del módulo como "__bar_MOD_a", son específicos del compilador y no están destinados a ser utilizados directamente. Pase 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 <dlfcn.h>
#include <stdio.h>
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;
}
compilar 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 los retornos en sus subrutinas Fortran, solo oscurecen el código.