Not having expected behaviour in Fortran-C interoperability

Hi everyone,

I’m not having the expected behavior, surely because I am missing something.

Basically the situation is like:

! CALLING PROGRAM
integer, target :: ierr = -1
call bsacl_Init(ierr)

! C-interf MODULE PROCEDURE
   module subroutine bsacl_Init(ierr)
      integer, intent(inout), target :: ierr
      call bsaclInit(c_loc(ierr))
   end subroutine

! ACTUAL C-core CODE
void bsaclInit(int *ierr)

But at the time I enter the C code, *ierr holds random garbage (expecting -1). And even changing it inside bsaclInit(), then exiting bsacl_Init(), ierr still holds -1, i.e. did not get modified (even though ierr in c-code is).

Shouldn’t c_loc() work normally with scalar fortran variables? It looks like is not working as in the C counterpart bsaclInit(&ierr_);.

I then changed (for both main Fortran program and interafce module) to /iface:stdcall. This led to correct behavior up to bsacl_Init(). Then, returning to the main program right after, ierr returned to hold -1.

Any advice is more than welcome :slight_smile:

cloc(ierr) returns the address of ierr. The Fortran compiler then creates a temporary variable on the stack that contains this address, and then passes the address of the temporary variable. So basically it passes the address of the address of ierr, and on the C side you should have

void bsaclInit(int **ierr)

Alternatively, without changing the C interface, your call should be:

call bsaclInit(ierr)

Note that you are assuming that a Fortran integer maps to a C int. Although true most of time, this is not guaranteed. A more robust code would use the C binding features:

   subroutine bsacl_Init(ierr)
      use iso_c_binding
      integer, intent(inout) :: ierr
      interface 
          subroutine bsaclInit(ierr) bind(C)
              integer(c_int) :: ierr
          end subroutine
      end interface
      integer(c_int) :: ierr_c
      ierr_c = ierr
      call bsaclInit(ierr_c)
      ierr = ierr_c
   end subroutine
2 Likes

Thanks @PierU , I get it now.
I was assuming the Fortran compiler was passing the value of the temporary c_loc(ierr), instead of its address. Strange because actually in bsacl_init() I was then having the right behavior (after the call, ierr became 0).
It was at the time of exiting, that in the main program ierr returned to hold -1, because at that point, with stdcall, the compiler did a copy (instead of normally passing the address).

Have to study this more.

In fact, I got it to work with my original approach.

After thinking about it, the problem was in the Fortran procedure interafce bound to the actual C code:

interface
      subroutine bsaclInit(ierr) bind(c, name="bsaclInit")
         import c_ptr
         type(c_ptr) :: ierr
      end subroutine

Of course, here it was passing the address of the temporary.

I had to change to:

interface
      subroutine bsaclInit(ierr) bind(c, name="bsaclInit")
         import c_ptr
         type(c_ptr), value :: ierr
      end subroutine

Thanks @PierU for your suggestion, surely I will keep it in mind !!

It wasn’t clear if you had an explicit interface or for the C routine. I assumed you were using an implicit interface.

Although describing the interface with type(c_ptr), value :: ierr is (I think) equivalent to integer(c_int) :: ierr, I prefer the latter: the compiler can check the type of the actual argument when calling the routine, whereas c_loc() of virtually any object/type will be OK for the compiler. And it makes clear that the C routine is expecting a pointer to an int and not a generic pointer.