将数组从C/C++传递给Fortran,并返回一个计算的数组

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

本文关键字:数组 返回 一个 计算 Fortran C++      更新时间:2023-10-16

我正试图将一个数组从C/C++传递到Fortran 2003模块中,并将计算出的值返回到C/C++中。我已经能够很好地传递和返回单个值(标量),但来回获取数组很困难。我发现了许多关于标量值的线程,并且我已经成功地使这些线程工作起来。

我已经根据工作标量函数对基于数组的函数进行了建模。

我正在使用gcc/gfortran。

这是Fortran模块(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

和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;
}

最后但同样重要的是,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

根据当前Fortran的规则(Fortran 2008,但这与Fortran 2003中引入C互操作性时的规则相同),如果Fortran过程有一个假定形状的伪参数,则它不能与C互操作(其他限制也适用)。在代码degC中,函数DegCtoF中的伪参数,声明为

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

就是这样一件事。

因此,在F2003下,您不可能拥有这样一个可互操作的功能。这就是事情变得棘手的地方。

在F2015的拟议草案中(基于ISO TS29113 Fortran与C的进一步互操作性),是可互操作的。这种语法(我认为)得到了最新版本的gcc的支持,这就是为什么gfortran没有拒绝该代码的原因。

(TS)然而,与这种具有假定形状参数的过程的标准化互操作需要在C端使用ISO_Fortran_binding.h中描述的C描述符,而这不是在gcc中实现的。要进行这样的交互,需要直接理解gcc数组描述符。

但你很幸运。在您的情况下,您实际上不需要使用假定形状伪参数:您可以使用显式形状伪参数,这样的互操作是F2003的一部分。您所需要做的就是传递数组的大小。

无论哪种方式,一个可互操作的函数都必须返回一个标量结果,因此您还需要转移到一个子例程,如innoSPG给出的答案所示。

最后,我要提到你使用

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

在模块中。

这些是可互操作的全局变量(通过链接关联)。Fortran代码中没有引用这些变量:虚设和函数结果不是这些东西。


在上面的这个简单的例子中,以及另一个答案中,人们会很高兴地拥有一个类似的子程序

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

但是这可能是描述来自CCD_ 4的C描述符的使用的好机会。不过,请注意,在短期内,gfortran不支持这种方法。

考虑Fortran源

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

(为了简单起见,我假设degF的内存管理都是在C端完成的——当然可以扩展到假设大小的数组之外)。为了使该子例程可互操作,对应于degC的参数必须是指向CFI_cdesc_t的指针。

取C码(带大小幻数)

#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;
}

这里CFI_establish建立了一个合适的C描述符DegreesC_Fdesc,它可以对应于假定形状的Fortran伪参数。在Fortran子程序中,评估传入数组的大小完全没有问题。

在francescalus确认这一点之前,我想说,据我所知,这有点过时,互操作性不允许您尝试使用数组。此外,在编码时,一些好习惯总是至关重要的。例如,在fortran中使用implicit none强制声明所有变量,然后再使用它们。在语言允许的情况下使用命名常量,例如在fortran中用作数组大小的2

下面是您的代码的修改版本,它应该做一些您想要实现的事情。

//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;
}