Iso_c_binding: pass an array from c to fortran (Edit: python interop content)

Hey everyone,

I’m having trouble figuring out how to pass an array from c to fortran.

Here’s an example of some C and fortran code that illustrates what I’m trying to do, but does not work:

extern void trivial(float*, int);
int main(void)
{
	float f[] = {1,2,3,4};
	float * fptr = &f[0];	

	trivial(fptr, 4);
}

module stuff
        use iso_c_binding
contains
        subroutine trivial(arr, n) bind(c)
                integer(c_int) :: n
                real(c_float)  :: arr(n)

                arr = arr+1
                print *, arr
        end subroutine
end module

It compiles alright, by compiling them separately to .o files and then linking them with gcc -lgfortran, but it segfaults when run. Does anyone know how to get this to work?

Thanks

change

to
integer(c_int), value :: n
and it should work

3 Likes

:man_facepalming:

Thank you very much. That fixed it

@hsnyder and any readers needing to interoperate Fortran codes with a companion C processor:

You may note many of the feature sets in Fortran starting with Fortran 90 thru’ Fortran 2018 can really prove useful in scientific and technical computing contexts, classic examples would be ASSUMED-SHAPE arrays, dummy arguments with ALLOCATABLE attribute, etc. With this in mind, note a “trivial” subprogram in modern Fortran can be as follows where the array descriptors are part of the dummy argument itself thereby making it unnecessary to pass additional arguments:

   ..
   subroutine Fsub( x ) bind(C, name="Fsub")
      ! Argument list
      real(kind=CF), intent(inout) :: x(:)  !<-- assumed-shape
..

Then if your strengths are in programming in C, C++, note with some additional effort, you can layer your C code to interoperate with Fortran’s strengths using the enhanced interoperability facility introduced in Fortran 2018 via “ISO_Fortran_binding.h” header. The standard for Fortran provides details on this, as does the book by Metcalf et al., “Modern Fortran Explained”

Here is a modified example of your code: as you will notice, it is an overkill in simple scenarios, but it can prove handy when it comes to consuming performant Fortran codes with a companion C processor.

#include <stdlib.h>
#include <stdio.h>
#include "ISO_Fortran_binding.h"

extern void Fsub(CFI_cdesc_t *);

int main(void)
{
   enum N { N = 4 };
   float x[N];
   int i;
   CFI_CDESC_T(1) dat; // employ a macro defined in Fortran binding header
   CFI_index_t ext[1]; // employ a type defined in Fortran binding header

   ext[0] = (CFI_index_t)N;
   i = CFI_establish((CFI_cdesc_t *)&dat, x, CFI_attribute_other,
                      CFI_type_float, 0, (CFI_rank_t)1, ext);

   Fsub((CFI_cdesc_t *)&dat );
   printf("In C main: following call to Fsub, x = \n");
   for (i=0; i<N-1; i++) printf("%f, ", x[i]); printf("%f\n", x[N-1]);

   return EXIT_SUCCESS;
}
module m
   use, intrinsic :: iso_c_binding, only : CF => c_float
contains
   subroutine Fsub( x ) bind(C, name="Fsub")
      ! Argument list
      real(kind=CF), intent(inout) :: x(:)
      integer :: i
      print *, "In Fsub: shape(x) = ", shape(x)
      x = [( real(i,kind=CF), i=lbound(x,dim=1),ubound(x,dim=1) )]
   end subroutine 
end module 

C:\Temp>ifort /c /standard-semantics /warn:all /stand:f18 m.f90
Intel® Fortran Intel® 64 Compiler Classic for applications running on Intel® 64, Version 2021.1 Build 20201112_000000
Copyright © 1985-2020 Intel Corporation. All rights reserved.

C:\Temp>cl /c /EHsc /W3 c.c
Microsoft ® C/C++ Optimizing Compiler Version 19.26.28806 for x64
Copyright © Microsoft Corporation. All rights reserved.

c.c

C:\Temp>link c.obj m.obj /subsystem:console /out:p.exe
Microsoft ® Incremental Linker Version 14.26.28806.0
Copyright © Microsoft Corporation. All rights reserved.

C:\Temp>p.exe
In Fsub: shape(x) = 4
In C main: following call to Fsub, x =
1.000000, 2.000000, 3.000000, 4.000000

C:\Temp>

1 Like

I was wondering a few days ago on what were the motives to introduce the ISO_Fortran_binding.h which opens up the array API? Was this a feature requested by Fortran application developers, or was it ultimately pushed by compiler developers?

Do you know of any publically available efforts to build a higher level API into Fortran objects?

What I have in mind is something like:

// fortran_array.cpp

#include "ISO_Fortran_binding.h"
#include <iostream>

template<typename T>
struct FortranArray
{
    const CFI_cdesc_t *obj; // A const reference to rank 2 Fortran array (can be discontiguous with regular strides).

    FortranArray(const CFI_cdesc_t *obj_) : obj(obj_) {}

    // Returns the i'th component of the j'th column in the array:
    inline T operator()(const size_t i, const size_t j) const
    {
        char *elt = (char *) obj->base_addr;
        elt += (i*obj->dim[0].sm + j*obj->dim[1].sm);
        return *(T *) elt;
    }

    size_t size(const size_t d) {
        return obj->dim[d].extent;
    }

};

extern "C" {

void print_array(CFI_cdesc_t *array) {

    // View into a Fortran array
    FortranArray<double> a(array);

    for (size_t i = 0; i < a.size(0); i++) {
        for (size_t j = 0; j < a.size(1); j++) {
            std::cout << a(i,j) << " ";
        }
        std::cout << "\n";
    }
}

}

The driver program in Fortran:

! test.f90

module print_mod
  use, intrinsic :: iso_c_binding, only: c_double
  implicit none

  interface
    subroutine print_array(A) bind(c,name="print_array")
      import c_double
      real(c_double), intent(in) :: A(:,:)
    end subroutine
  end interface

end module

program test

  use print_mod
  implicit none

  real(c_double) :: A(3,2)

  A = reshape([1,2,3,4,5,6],shape(A))
  
  call print_array(A)

end program

Compiling, linking, and output:

$ g++ fortran_array.cpp -c
$ gfortran fortran_array.o test.f90 -o test -lstdc++
$ ./test
1 4 
2 5 
3 6 

Edit: I added a template parameter to the FortranArray struct.

1 Like

My question above was addressed to @FortranFan.

Re: your first question about enhanced interoperability with C in Fortran 2018, the WG5 site can provide you with a document trail on this work post Fortran 2003, especially with what a focused development with a technical specification (TS) project 29113. You will notice there was motivation from the users, particularly the MPI community, to make it easier to access from C the Fortran APIs with many of the advantageous features of Fortran such as dummy arguments with assumed-shape arrays and other attributes such as ALLOCATABLE.

As to your other question, I’m not aware of any publicly available efforts to encapsulate Fortran standard types (and methods) in ISO_Fortran_binding.h.

@FortranFan’s explanation inspired me to play around a bit. I came up with the following, as an interesting way to convert Python arrays to Fortran arrays, using pybind11 (which is a header only library that makes it easy to call c++ from python).

I’m aware of the existence of f2py, which would eliminate the need for a c++ wrapper, but at work we use pybind11 already for wrapping C kernels to be called from python, and I’ve had mixed experiences with f2py, f90wrap, and such tools. Not saying they’re bad, but for me this worked better.

trivial.f90

module trivial_fortran
        use iso_c_binding
contains
        subroutine trivial(arr) bind(c)
                real(c_float), intent(inout)      :: arr(:)

                arr = arr + 1
        end subroutine
end module

pybind_fortran.h

#pragma once

#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>

#include<stdexcept>
#include<string>

extern "C" {
#include<ISO_Fortran_binding.h>
}

#define CONCAT(a,b) a ## b
#define FARRAY_PTR     CFI_cdesc_t*
#define FARRAY         CFI_CDESC_T(CFI_MAX_RANK)
#define FARRAY_TYPE_T  CFI_type_t
#define FARRAY_TYPE(x) CONCAT(CFI_type_,x)


void py_array_to_fortran(pybind11::array & pyarr, FARRAY_PTR farr, FARRAY_TYPE_T type)
{
	int rc = CFI_establish((CFI_cdesc_t*)farr, 
                           pyarr.request().ptr, 
                           CFI_attribute_other, 
                           type, 
                           0, 
                           (CFI_rank_t)pyarr.request().shape.size(), 
                           (const CFI_index_t *)pyarr.request().shape.data());
	// error check omitted for brevity
}

triv_wrap.cpp

#include<pybind11/pybind11.h>
#include<pybind11/numpy.h>

#include "pybind_fortran.h"

namespace py = pybind11;


extern "C" void trivial(FARRAY_PTR);
void trivial_wrap(py::array_t<float, py::array::c_style> arr)
{

	FARRAY dat;
	py_array_to_fortran(arr, (FARRAY_PTR) &dat, FARRAY_TYPE(float));

	trivial((FARRAY_PTR) &dat);
}

PYBIND11_MODULE(triv_wrap, m) {
	m.def("trivial", &trivial_wrap);
}

test.py

import triv_wrap as tw
import numpy as n

a = n.arange(9, dtype=n.float32)
print(a)
tw.trivial(a)
print(a)

Makefile

triv_wrap.so:
	g++ -fpic -c $(shell python3 -m pybind11 --includes) triv_wrap.cpp 
	gfortran -fpic -c trivial.f90
	g++ -shared triv_wrap.o trivial.o -o triv_wrap.so -lgfortran

clean:
	rm *.o *.mod *.so -f

test:
	python3 test.py

.PHONY: clean test

Running make && make test:

python3 test.py
[0. 1. 2. 3. 4. 5. 6. 7. 8.]
[1. 2. 3. 4. 5. 6. 7. 8. 9.]
1 Like