I would like to write functions or subprograms that return a derived type containing arrays with a size that is unknown at compile time. The rank is known. I would like to call these from C. One method on the fortran side is perhaps to use a parameterised derived type. Can I call this from C? Is there another preferred method? Can I make this a function rather than a subroutine? My fortran code so far is given below:
module dt
use iso_c_binding
implicit none
type, bind(c) :: results(n)
integer(c_int), len :: n
real(c_double), dimension(n) :: myarray
end type results
contains
subroutine set_results(res, n)
use iso_c_binding
integer(c_int) :: n
integer(c_int) :: i
type(results(n)), intent(out) :: res
do i=1,n
res%myarray(i) = i
end do
end subroutine set_results
end module dt
program use_dt
use dt
implicit none
type(results(10)) :: myresults
call set_results(myresults, 10)
print *,myresults
end program use_dt
CodePudding user response:
A solution is to declare the arrays as type(c_ptr)
in the Fortran derived type, and convert them to Fortran pointers with the c_f_pointer()
routine. AFAIK the allocation has to be performed on the C side.
module dt
use iso_c_binding
implicit none
type, bind(c) :: results
integer(c_int) :: n
type(c_ptr) :: myarray
end type results
contains
subroutine set_results(res) bind(c)
use iso_c_binding
integer :: i
type(results), intent(out) :: res
real(c_double), pointer :: myarrayf(:)
call c_f_pointer(res%myarray,myarrayf,[res%n])
do i=1,res%n
myarrayf(i) = i
end do
end subroutine set_results
end module dt
The tedious part is that you have to call c_f_pointer() for each individual array in the derived type.
CodePudding user response:
Let's assume for the time being that you can allocate storage of the correct size in the calling program (the C desktop application). In other words all dimensions are known before entering the Fortran subprogram.
For interoperable types, the easiest option is to use assumed-size arrays, meaning array dimensions are need to be passed explicitly (or are assumed to be known constants):
module interop
use, intrinsic :: iso_c_binding, only: &
rp => c_double, &
ip => c_int
implicit none
contains
!> Set results in an assumed-size array
!> void set_results_v1(int n, double res[]);
subroutine set_results_v1(n,res) bind(c)
integer(ip), value :: n
real(rp), intent(out) :: res(*) ! <- contiguous storage
integer(ip) :: i
do i = 1, n
res(i) = i
end do
end subroutine
end module
For a 2D array of size n × m, your procedure interface might look like this:
! void fill_random_2d(int n, int m, int lda, double *a) // in C/C
! void fill_random_2d(int n, int m, int lda, double a[][lda]) // in C99
subroutine fill_random_2d(n,m,lda,a) bind(c)
integer(ip), value :: n, m, lda
real(rp), intent(inout) :: a(lda,*) !
call random_number(a(1:n,1:m))
end subroutine
The integer lda
can be used to account for any array padding. With 2D arrays you need to be careful with differences due to opposite storage order, i.e. Fortran (column-major) vs C/C (row-major). Arguably, it's easier to assume column-oriented storage in C and do multi-dimensional indexing manually if necessary.
The newer, but potentially more powerful option is to the use the C descriptors introduced in Fortran 2018. Descriptors can be used to create Fortran objects in C/C which have no direct equivalent. Descriptors allow you to interoperate also with assumed-shape, allocatable, and pointer arrays.
Using an assumed-shape array, your routine is written as
!> Set results using assumed-shape array
!> (Fortran 2018 C-descriptor needed for interoperation)
subroutine set_results_v2(res) bind(c)
real(rp), intent(out) :: res(:) ! <- contiguous or strided storage
integer :: i
do i = 1, size(res)
res(i) = i
end do
end subroutine
Here's how the calling the Fortran procedure will look like in C :
// For GCC compile using
// g -Wall main.cpp interop.o -lgfortran
// where interop.o contains the external routine.
#include <vector>
#include <iostream>
#include <ISO_Fortran_binding.h>
/**
* Fortran routine prototypes
*/
extern "C" {
void set_results_v2(CFI_cdesc_t *res);
}
int main(int argc, char const *argv[])
{
const int N = 10;
std::vector<double> myresult(N);
CFI_CDESC_T(1) res_desc; /* Rank-1 array descriptor */
// The C descriptor is an opaque struct that stores
// information about a Fortran objects which have
// no proper equivalent in C. Such structs can be
// used as dummy arguments in inter-language calls.
// The descriptor can only be modified by functions provided
// in the header "ISO_Fortran_binding.h". These functions
// expect a pointer to the array descriptor.
auto res = reinterpret_cast<CFI_cdesc_t *>(&res_desc);
// First we establish a local array of extents
// In this case we are wrapping a std::vector, which
// can be seen as a 1-dimensional array.
CFI_index_t res_extents[1] = { N };
// Now we establish the C-descriptor.
// In this case `res` will be a rank-1 array
// of type double referring to the data
// in the std::vector `myresult`.
int err = CFI_establish(
res, /* pointer to the descriptor */
myresult.data(), /* pointer to the data */
CFI_attribute_other, /* attribute */
CFI_type_double, /* type of */
sizeof(double), /* size of element */
1, /* rank */
res_extents); /* array extents */
// In case something went wrong, a non-zero
// value is returned, which can be used for
// error-checking
if (err != CFI_SUCCESS)
return 1;
// Now we have a complete descriptor which
// can be passed to a Fortran routine.
set_results_v2(res);
for(auto v : myresult) {
std::cout << v << ' ';
}
std::cout << '\n';
return 0;
}
If you'd like to pass a 2-dimensional assumed-shape array you just need to adjust the rank-constant (replace 1 with 2), and adjust the extents, e.g.
CFI_index_t res_extents[2] = { N, M };
It is also possible to pass array slices, provide custom array bounds, and more. To learn more about this approach I would recommend reading Metcalf, Reid, & Cohen (2018), Modern Fortran Explained, Oxford University Press, pgs. 397-421.