8

I am trying to pass an array from C/C++ into a Fortran 2003 module and get the calculated values back into C/C++. I've been able to pass and return single values (scalars) just fine, but getting an array back and forth is proving difficult. I've found many threads on scalar values and I've been successful at making those work.

I've modeled my array based functions after my working scalar functions.

I am using gcc/gfortran.

Here's the Fortran module (ConvertUnitsLib.f03).

module ConvertUnitsLib

use :: iso_c_binding ! for C/C++ interop
real(c_double), bind(c) :: degF, degC

public DegCtoF

contains

!
! Convert temperature degrees Celsius Fahrenheit
!
real(kind = c_double) function DegCtoF(degC) result(degF) &
    & bind(c, name = "DegCtoF")

    real(c_double), intent(in), dimension(:) :: degC
    real(c_double), dimension(size(degC)) :: degF

    do i = 1, size(degC)
        degF(i) = ( degC(i) * 1.8 ) + 32
    end do

end function DegCtoF


! End of module
end module ConvertUnitsLib

And the C/C++, (CFort.cpp)

#include <stdio.h>

#ifdef __cplusplus
extern"C" {
#endif
    double DegCtoF(double *[]);
#ifdef __cplusplus
}
#endif


/**********************************************************************/


int main(int argc, char *argv[])
{
    printf("C/C++ and Fortran together!\n");

    double DegreesC[2] = {32, 64};
    double DegreesF[2];

    DegreesF = DegCtoF(&DegreesC);
    printf("%3.1f [C] = %3.1f [F]\n", DegreesC, DegreesF );

    return 0;
}

And last but not least, the Makefile

# C++ directives
CC=g++
CFLAGS=-std=c++11

# Fortran directives
FC=gfortran
FFLAGS=-std=f2003

all: clean
    $(FC) $(FFLAGS) -c -fcheck=all ConvertUnitsLib.f03
    $(CC) $(CFLAGS) -c CFort.cpp
    $(FC) $(FFLAGS) ConvertUnitsLib.o CFort.o -o convert

clean:
    rm -f *.o
    rm -f *.mod
francescalus
  • 30,576
  • 16
  • 61
  • 96
Adam A.
  • 320
  • 3
  • 10
  • Well, I am not sure about Fortran, but in C and C++, passing an array (which is actually passing the address of the first element) loses all size information, so your Fortran routine knows nothing about the actual number of elements the array contains. I would expect two parameters, the array (or pointer to the buffer), and the number of elements. – PaulMcKenzie Jan 02 '16 at 02:12
  • From your tags I assume you are using gfortran/gcc, but can you confirm? Passing an assumed shape argument, such as `degC` is not part of Fortran 2003 C interoperability and requires much more effort. The aspect currently expected to be part of F2015 (the ISO TS) is not suported by gcc. In the absence of such support, use the comment by @PaulMcKenzie and use an explicit shape array with size also passed. – francescalus Jan 02 '16 at 02:20
  • @francescalus, Yes I'm using gfortran/gcc. When you mean more effort, Do I need to pass the size of the array also? I know that when C calls the Fortran function it passes the scalar values by a pointer. Do I just need to pass the size also, and then can it (Fortran) figure the rest out? – Adam A. Jan 02 '16 at 02:26
  • @francescalus, ok that makes sense. I'm working on that code now, but how do I pass it back from Fortran to C? – Adam A. Jan 02 '16 at 02:39
  • @francescalus, the only way I could get the scalar variables to move between languages was to declare them public. – Adam A. Jan 02 '16 at 02:41
  • Thank you @francescalus for the help! +1 for the explanations of what is going on. – Adam A. Jan 02 '16 at 02:59
  • There are also many prior answers on using the ISO C BInding to pass arrays, e.g., http://stackoverflow.com/questions/28697754/fortran-c-interoperability-and-float-arrays, http://stackoverflow.com/questions/28003625/passing-fortran-arrays-to-c, http://stackoverflow.com/questions/26678961/iso-c-binding-calling-c-routine-from-fortran-with-doubles-and-arrays, http://stackoverflow.com/questions/22310734/passing-fortran-integer-array-to-c-subroutine-only-first-element-passed – M. S. B. Jan 02 '16 at 03:25
  • It isn't worth mentioning in my answer, but your Fortran function statement is incorrect: if you declare the type of the result (`real(c_double)`) you can't also declare the result type in a later line. See http://stackoverflow.com/q/24170024, for example. – francescalus Jan 02 '16 at 03:33
  • There is no languages C/C++ This looks like plain C, so you should remove the C++ tag. – too honest for this site Jan 02 '16 at 08:28

2 Answers2

9

Under the rules of current Fortran (Fortran 2008, but this is the same for when C interoperability was introduced in Fortran 2003), a Fortran procedure is not interoperable with C if it has an assumed shape dummy argument (other restrictions also apply). In your code degC, the dummy argument in the function DegCtoF, declared as

real(c_double), intent(in), dimension(:) :: degC

is such a thing.

So, under F2003 you cannot have such an interoperable function. Which is where things get tricky.

In the proposed draft for F2015 (based on the ISO TS29113 Further Interoperability of Fortran with C) such a thing is interoperable. And this syntax is (I think) supported by recent versions of gcc which is why the code is not rejected by gfortran.

(TS) Standardized interoperation with such a procedure with an assumed shape argument, however, requires using the C descriptor described in ISO_Fortran_binding.h on the C side which is not implemented in gcc. To do such interaction instead requires understanding the gcc array descriptor directly.

But you're in luck. In your case you don't really need to use an assumed shape dummy argument: you can use an explicit shape dummy argument and such interoperation is part of F2003. All you need to do is pass the size of the array.

Either way, an interoperable function must return a scalar result, so you'll also want to move to a subroutine, as given in the answer by innoSPG.

Finally, I'll mention your use of

real(c_double), bind(c) :: degF, degC

in the module.

These are interoperable global variables (through linkage association). You don't reference these variables in the Fortran code: the dummy and the function result are not these things.


In this simple case from the above, and the other answer, one will happily have a subroutine like

subroutine DegCtoF(n, degC, degF) bind(c,name='DegCtoF')
  ...
end subroutine

but this is perhaps a good opportunity to describe the use of the C descriptor from ISO_Fortran_binding.h. Note, though, that in the immediate term gfortran does not support this approach.

Consider the Fortran source

subroutine DegCtoF(degC, degF) bind(c,name='DegCtoF')
   use, intrinsic :: iso_c_binding, only : c_double
   implicit none
   real(c_double), intent(in), dimension(:) :: degC
   real(c_double), intent(out), dimension(*) :: degF

   degF(1:SIZE(degC)) = degC*1.8+32
end subroutine DegCtoF

(for simplicity I'm going to assume that the memory management of degF is done all on the C side - naturally one could extend beyond the assumed size array). For this subroutine to be interoperable the argument corresponding to degC must be a pointer to CFI_cdesc_t.

Take the C code (with size magic numbers)

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

void DegCtoF(CFI_cdesc_t*, double*);

int main(int argc, char *argv[])
{
    printf("C and Fortran together!\n");

    CFI_CDESC_T(1) DegreesC_Fdesc;
    CFI_index_t extent[1] = {2};
    CFI_rank_t rank = 1;

    double DegreesC[2] = {32, 64};
    double DegreesF[2];

    CFI_establish((CFI_cdesc_t*)&DegreesC_Fdesc, &DegreesC, CFI_attribute_other, 
                  CFI_type_double, 2*sizeof(double), rank, extent);

    DegCtoF((CFI_cdesc_t*)&DegreesC_Fdesc, DegreesF);
    printf("%3.1f [C] = %3.1f [F]\n", DegreesC[0], DegreesF[0] );
    printf("%3.1f [C] = %3.1f [F]\n", DegreesC[1], DegreesF[1] );

    return 0;
}

Here CFI_establish establishes a suitable C descriptor DegreesC_Fdesc which can correspond to the assumed shape Fortran dummy argument. Inside the Fortran subroutine there is no problem at all assessing the size of the incoming array.

Community
  • 1
  • 1
francescalus
  • 30,576
  • 16
  • 61
  • 96
6

Before francescalus confirms it, I was going to say that from what I know that was a little bit old, the interoperability does not permit what you are trying to do with arrays. In addition, some good habits are always critical when coding. For example using implicit none in fortran to force the declaration of all variables before they are used. The use of named constant when the language permits it, for example the 2 that you are using as array size in fortran.

Below is a modified version of your code that should do something like what you want to achieve.

//Fortran

module ConvertUnitsLib

use :: iso_c_binding ! for C/C++ interop
!real(c_double), bind(c) :: degF, degC
implicit none

public DegCtoF

contains

!
! Convert temperature degrees Celsius Fahrenheit
!
subroutine DegCtoF(degC, degF, n)&
    bind(c, name = "DegCtoF")

    integer, intent(in) :: n
    real(c_double), intent(in), dimension(n) :: degC
    real(c_double), intent(out), dimension(n) :: degF
    integer :: i

    do i = 1, n
        degF(i) = ( degC(i) * 1.8 ) + 32
    end do

end subroutine DegCtoF

// C++

#include <stdio.h>

#ifdef __cplusplus
extern"C" {
    #endif
    double DegCtoF(double [], double [], const int *);
    #ifdef __cplusplus
}
#endif


/**********************************************************************/


int main(int argc, char *argv[])
{
    const int N = 2;
    printf("C/C++ and Fortran together!\n");

    double DegreesC[N] = {32, 64};
    double DegreesF[N];

    DegCtoF(DegreesC, DegreesF, &N);
    for(int i = 0; i<N; i++){
        printf("%d : %3.1f [C] = %3.1f [F]\n", i, DegreesC[i], DegreesF[i] );
    }

    return 0;
}
innoSPG
  • 4,588
  • 1
  • 29
  • 42
  • That's what I'm looking for. Just tested the code and it works great. What I'm not sure of is the safety of using a subroutine though. Coming from IDL, subroutines allow the code block to modify any internal variable within the block. Essentially a subroutine is executed inline. Is the same in Fortran? What I'm curious about is if I have say a variable named NUM in the subroutine, will the subroutine be able to modify that variable in the global sense? – Adam A. Jan 02 '16 at 03:01
  • Turning it into a subroutine is one thing I forgot to mention. That is the way I use to do it few years ago. Thanks to @francescalus to mention it in his answer. I know nothing about IDL. FOr the inline execution, I do not understand your question. – innoSPG Jan 02 '16 at 17:27
  • I think @AdamA. fears that subroutines might be more like C macros. A simpler mechanism that would not introduce a new scope and could modify stuff outside of itself. I don't think you should worry about that, subroutines have the same powers over their arguments as functions do (they both can modify their arguments depending on their types). In C terms, Fortran subroutines are void functions and Fortran functions are all the non-void functions. With the added quirk that calling subroutines requires the call keyword. – Philippe Carphin Apr 19 '22 at 15:18