从 C++ 调用带有可选参数的 Fortran 子例程

Calling Fortran subroutines with optional arguments from C++

如何在使用可选参数的 C++ header 中引用 Fortran 函数?我会在 header 中为每种可能的调用组合提供原型吗?或者这甚至可能吗?

例如 Fortran:

subroutine foo(a, b, c) bind(c)
   real, intent(in), optional :: a, b, c
   ...
end subroutine foo

这是不可能的,至少是可移植的,除非你制作子程序bind(C)

一旦你做到了bind(C),它只是传递一个指针,在 C 端可以为 NULL。

subroutine foo(a, b, c) bind(C, name="foo")
   use iso_c_binding, only: c_float
   real(c_float), intent(in), optional :: a, b, c
   ...
end subroutine foo

(为了更好的可移植性,我使用了 iso_c_binding 模块中的 real(c_float),但这与这个问题有些无关)

在 C(++) 中

extern "C"{
  void foo(float *a, float *b, float *c);
}

foo(&local_a, NULL, NULL);

然后你可以创建一个调用 foo 并使用 C++ 样式可选参数的 C++ 函数。

Fortran 在技术规范 ISO/IEC TS 29113:2012 中允许使用此功能,以进一步实现 Fortran 与 C 的互操作性,并随后合并到 Fortran 2018 中。

作为 ,在 Fortran 2018(和 Fortran 2008+TS29113)下,可以在 C 可互操作的 Fortran 过程中将 optional 属性用于虚拟参数。

在 Fortran 2008 下这是不可能的。一些编译器目前仍然不支持这个特性。使用这些编译器仍然(尽管需要更多工作)能够支持 "optional" 个参数。

问题的过程 foo 在 F2008 下不能与 C 语言互操作(即使 bind(C))。但是,在 F2008 下可以模仿这个想法:有一个带有 type(c_ptr) 参数的 C 互操作过程,它包装了所需的 Fortran 过程。这个可互操作的包装器可以检查空指针(使用 C_ASSOCIATED)以确定是否存在向前传递的参数 - 如果存在则传递取消引用的参数。

例如,具有 C 互操作包装器的 Fortran 端可能看起来像

module mod

  use, intrinsic :: iso_c_binding

contains

  subroutine foo_centry(a) bind(c,name='foo')
    type(c_ptr), value :: a
    real(c_float), pointer :: a_pass

    nullify(a_pass)
    if (c_associated(a)) call c_f_pointer(a, a_pass)
    call foo(a_pass)
  end subroutine foo_centry

  subroutine foo(a)
    real(c_float), optional :: a
  end subroutine foo

end module mod

在 Fortran 2018 下,我们在互操作接口中有这种对称性:如果过程是通过 Fortran 以外的方式定义的,但互操作接口有一个可选参数,那么在 F2018 下,我们得到的结果是用参数引用这个过程不存在意味着空指针被传递给过程。

在 F2008 下,我们也需要处理这一方面:我们再次使用 F2008 不可互操作的过程来做到这一点,该过程用 type(c_ptr) 个参数包装一个可互操作的过程:如果存在参数,则传递其地址;如果不是,通过 C_NULL_PTR.

这样的 F2008 代码可能看起来像

module mod
  use, intrinsic :: iso_c_binding

  interface
     subroutine foo_fentry(a) bind(c,name='foo')
       import c_ptr
       type(c_ptr), value :: a
     end subroutine foo_fentry
  end interface

contains

  subroutine foo(a)
    real(c_float), optional, target :: a

    if (present(a)) then
       call foo_fentry(c_loc(a))
    else
       call foo_fentry(c_null_ptr)
    end if
  end subroutine foo

end module mod

请注意此处因使用 c_loc 而造成的限制:在某些情况下,可能需要使用副本或采取其他保护措施。