How to access (dynamically allocated) Fortran arrays in C

倖福魔咒の 提交于 2019-12-03 04:00:49

In my opinion it is not good practice to try to access global data in Fortran library. It can be done using COMMON blocks, but they are evil and require statically sized arrays. Generally storage association is a bad thing.

Never access the module symbols as "__bar_MOD_a" they are compiler specific and not meant to be used directly. Pass poiters using functions and subroutines.

Pass the array as a subroutine argument. You can also allocate the array in C and pass it to Fortran. What can be also done is getting a pointer to the first element of the array. It will serve es the C pointer to the array.

My solution, for simplicity without the .so, it is trivial to add it:

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

main.c

#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;
}

compile and run:

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 

Note: There is no reason for the returns in your Fortran subroutines, they only obscure the code.

Many Fortran compilers use internally something called array descriptors - structures that hold the shape of the array (that is the size and range of each dimension as well as pointer to the real data). It allows for the implementation of things like assumed-shape array arguments, array pointers and allocatable arrays to work. What you are accessing through the __bar_MOD_a symbol is the descriptor of the allocatable array and not its data.

Array descriptors are compiler-specific and code that relies on specific descriptor format is not portable. Example descriptors:

Note that even those are specific to some versions of those compilers. Intel, for example, states that their current descriptor format is not compatible with the format used in Intel Fortran 7.0.

If you look at both descriptors you would see that they are largerly similar and that the first element is a pointer to the array data. So you would be able to easily read the data using double ** instead of double *:

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

Once again, this is not portable as the format of those descriptors might change in the future (although I doubt that the data pointer would be moved somewhere else than at the beginning of the descriptor). There is a draft specification that tries to unify all descriptor formats but it is not clear how and when it will be adopted by the different compiler vendors.

Edit: Here is how to use an accessor function that uses C_LOC() from the ISO_C_BINDING module to portably obtain a pointer to the allocatable array:

Fortran code:

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 code:

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

Result:

$ ./prog.x
   0.0000000000000000        3.1400000000000001        6.2800000000000002
 9.4199999999999999
0.000000 3.140000 6.280000 9.420000
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!