Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass arrays from C/C++ to Fortran and return a calculated array

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
like image 343
Adam A. Avatar asked Jan 02 '16 01:01

Adam A.


1 Answers

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.

like image 109
francescalus Avatar answered Sep 24 '22 03:09

francescalus