将标量和数组(任意维度)从 Fortran 传递到 C

Passing both scalars and arrays (of any dimensions) from Fortran to C

我有以下名为 show_value 的 Fortran 子例程,它调用名为 show_value 的 C 函数:

INTERFACE
    SUBROUTINE show_value(variable) BIND(C, name = "show_value")
        USE, INTRINSIC :: iso_c_binding
        TYPE(*) :: variable
    END SUBROUTINE
END INTERFACE

C函数show_value:

void show_value(const void *variable)
{
    printf("%d\n", *(int *) variable);
}

Fortran 子例程在向其传递标量时运行良好。示例:

INTEGER :: x
x = 12
call show_value(x)

这将调用 C 函数 show_value 并打印 12,这是正确的。

现在,根据 Fortran 文档,如果想要启用子例程 show_value 来接收数组(任意维度)而不仅仅是标量,行 TYPE(*) :: variable 应该更改为 TYPE(*), DIMENSION(..) :: variable.

进行此更改后,在执行以下 Fortran 代码时:

INTEGER, DIMENSION(3) :: y
y(1) = 15
y(2) = 17
y(3) = 19
call show_value(y)

C 函数 show_value 不再打印正确的消息(即打印随机数)。此外,我发现 C 函数接收的地址比原来的地址低 528(在 Fortran 中)。确认这一点:

void show_value(const void *variable)
{
    printf("%d\n", *(int *) (variable + 528));
}

... 打印出 15(正确的数字)。

知道这里发生了什么吗?

环境:Ubuntu 14.04 64 位,gfortran 4.9

虽然你的第一个案例,带有标量参数,可以与 void* 参数正确匹配,但是当调用的参数是假定等级(type(*), dimension(..) :: variable 代表)时,Fortran 过程不是可与具有相应正式参数 const void *variable.

的 C 过程互操作

而是需要使用CFI_cdesc_t机制:

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

void show_value(const CFI_cdesc_t* variable)
{
  printf("%d\n", *(int*) variable->base_addr);
}

您可以在 Fortran 2018 18.5.3 中找到详细信息。

然而,从本质上讲,这是一个包含 Fortran 实体很多细节的描述符。在这里,base_addr 是数据的开始,但您还会发现 allocatable/pointer/data 状态、排名、范围、类型。

唉,gfortran 4.9 不支持这个。如果它完全受支持,它将只在最新版本中。


或者,您可以避免使用假定秩假定类型实际参数,而是使用 c_loc 传递参数的 C 地址。不太优雅,但得到更广泛的支持:

use, intrinsic :: iso_c_binding, only : c_loc, c_ptr, c_int
interface
  subroutine show_value(variable) bind(c)
    import c_ptr
    type(c_ptr), value :: variable
  end subroutine
end interface

integer(c_int), target :: x, y(3)

x = 12
y = [15, 17, 19]

call show_value(c_loc(x))
call show_value(c_loc(y))

end

然而,这留下了 C 函数如何知道如何处理参数的问题。