【问题标题】:Does the abstract Fortran interface of a C callback function require bind(C) attribute?C 回调函数的抽象 Fortran 接口是否需要 bind(C) 属性?
【发布时间】:2019-07-19 10:19:04
【问题描述】:

考虑以下 Fortran 代码,其中模块 Foo_mod 中的 C 互操作子例程 runFoo4C(...) bind(C, name="runFoo") 将 C 回调函数指针 getLogFuncFromC() 作为参数,

module CallbackInterface_mod

    abstract interface
        function getLogFunc4C_proc(ndim,Point) result(logFunc) ! bind(C)
            use, intrinsic :: iso_c_binding, only : c_int32_t, c_double, c_int
            integer(c_int32_t), intent(in)  :: ndim
            real(c_double), intent(in)      :: Point(ndim)
            real(c_double)                  :: logFunc
        end function getLogFunc4C_proc
    end interface

end module CallbackInterface_mod

!***********************************************************************************************************************************
!***********************************************************************************************************************************

module Foo_mod

    interface
    module subroutine runFoo4C(ndim, getLogFuncFromC, inputString, inputStringLen) bind(C, name="runFoo")
        use, intrinsic :: iso_c_binding, only: c_int32_t, c_char, c_funptr, c_f_procpointer, c_size_t
        use CallbackInterface_mod, only: getLogFunc4C_proc
        implicit none
        integer(c_int32_t) , intent(in)                         :: ndim
        character(len=1, kind=c_char), dimension(*), intent(in) :: inputString
        integer(c_size_t) , intent(in)                          :: inputStringLen
        type(c_funptr), intent(in), value                       :: getLogFuncFromC
    end subroutine runFoo4C
    end interface

contains

    subroutine runFoo(ndim, getLogFunc, string)
        !use CallbackInterface_mod, only: getLogFunc_proc
        use CallbackInterface_mod, only: getLogFunc4C_proc
        use, intrinsic :: iso_fortran_env, only: RK => real64
        implicit none
        integer :: ndim
        procedure(getLogFunc4C_proc)    :: getLogFunc
        character(*), intent(in)        :: string
        real(RK)                        :: Point(ndim)
        character(:), allocatable       :: mystring
        Point = [1._RK,1._RK]
        write(*,*) "Hi again, this is a call from inside runFoo!"
        write(*,*) "getLogFunc(2,[1,1]) = ", getLogFunc(ndim,Point)
        write(*,*) "string = ", string
    end subroutine

end module Foo_mod

!***********************************************************************************************************************************
!***********************************************************************************************************************************

submodule (Foo_mod) Foo_smod

contains

    module subroutine runFoo4C(ndim, getLogFuncFromC, InputString, inputStringLen) bind(C, name="runFoo")

        use, intrinsic :: iso_c_binding, only: c_double, c_int32_t, c_char, c_funptr, c_f_procpointer, c_size_t
        use CallbackInterface_mod, only: getLogFunc4C_proc
        implicit none
        integer(c_int32_t) , intent(in)                         :: ndim
        character(len=1, kind=c_char), dimension(*), intent(in) :: InputString
        integer(c_size_t) , intent(in)                          :: inputStringLen
        type(c_funptr), intent(in), value                       :: getLogFuncFromC
        procedure(getLogFunc4C_proc), pointer                   :: getLogFunc
        real(c_double)                                          :: Point(ndim)
        character(:), allocatable                               :: inputString4tran
        integer                                                 :: i

        write(*,*) "InputString: ", InputString(1:inputStringLen)
        allocate( character(len=inputStringLen) :: inputString4tran )
        do i=1,inputStringLen
            inputString4tran(i:i) = InputString(i)
        end do
        write(*,*) "inputString4tran: ", inputString4tran

        ! associate the input C procedure pointer to a Fortran procedure pointer
        call c_f_procpointer(cptr=getLogFuncFromC, fptr=getLogFunc)
        Point = [1._c_double, 1._c_double]
        write(*,*) "Here we go: "
        write(*,*) "getLogFunc(ndim=2, [1._c_double, 1._c_double]): ", getLogFunc( ndim, Point )

        call runFoo(ndim, getLogFunc, inputString4tran)

    end subroutine runFoo4C

end submodule Foo_smod

这个回调函数的抽象Fortran接口,由上面代码中模块CallbackInterface_mod中的getLogFunc4C_proc()给出。现在的问题:

这个抽象接口是否需要bind(c) 属性才能符合fortran 标准?我自己幼稚的猜测是它不需要bind(c),因为它不会在接口中使用函数的全局标识符来调用,但是抽象接口只是确定了C回调函数的接口,一个指向它被传递给 Fortran 以便稍后从 Fortran 内部调用。

确实,在抽象接口中注释掉这个bind(c) 属性不会导致任何使用ifort(18.0.2 Windows 编译器)的编译或运行时错误。

如果不需要,那么在这个抽象接口中如何声明变量呢?它们是否需要由 iso_c_binding 内在模块中的符合 C 的种类声明?

【问题讨论】:

  • 这似乎是一个相当复杂的例子:如果你能简化它可能会使讨论更容易。
  • 我同意。它直接取自我编写的一组示例。将尝试简化它。但核心问题是关于最顶部CallbackInterface_mod 模块的抽象接口中的bind(c) 属性,以及注释掉bind(c) 是否重要。整个代码是独立的,可以使用 Intel ifort 或 gfortran 进行编译。

标签: c fortran intel-fortran fortran-iso-c-binding


【解决方案1】:

c_f_procpointer 的规范在 Fortran 2008 和 Fortran 2018 中有所不同。不管怎样,我们看一下声明

call c_f_procpointer(cptr=getLogFuncFromC, fptr=getLogFunc)

在 Fortran 2008 下,fptr 参数的接口必须与 cptr 参数的目标可互操作。要实现互操作,接口必须具有bind 属性。

在 Fortran 2018 中,此要求已放宽,但仅当 cptr 参数是对 c_funloc 的引用的结果时,我们才允许为 fptr 参数提供不可互操作的接口。有可能发生这种情况,具体取决于调用 runFoo4C 的方式。

在任何一种情况下,编译器都不需要诊断任何违反要求的情况(在最后一种情况下,很容易看出这可能是多么棘手)。

【讨论】:

    【解决方案2】:

    抽象接口中 BIND(C) 的存在(或不存在)改变了过程指针的特性,但这样做的方式是该程序没有透露。因为您通过从 C_FUNPTR 转换的指针调用 getLogFunc,所以如果在抽象接口中省略 BIND(C),您可以防止编译器注意到不匹配。例如,如果该过程有一个字符 (*) 参数,那么不匹配会发生很多不好的事情。

    BIND(C) 本身在抽象接口中是可以的,只要你不说 NAME=。由于它改变了过程的调用方式,如果被调用的过程是可互操作的,则必须指定它。

    关于“如果不需要,那么这个抽象接口中的变量声明怎么样?它们是否需要由 iso_c_binding 内部模块中的符合 C 的种类声明?”,你犯了在内部定义中混淆定义的常见错误可互操作的模块 ISO_C_BINDING。该模块中的种类常量只是数字,它们没有什么神奇之处。您需要使实际参数和虚拟参数在类型、种类和等级上匹配(有一些例外。)

    【讨论】:

    • 史蒂夫,再次感谢。你的回答非常清楚。那么bind(c)对抽象接口的要求就比较受限制了。这意味着传递给可与 C 互操作的 Fortran 子例程的所有 Fortran 过程指针还必须包含 bind(c)(以及符合 iso_c_binding 中所有 c_* 的类型参数,换句话说,可与 C 互操作。这从编程的角度来看,这没什么大不了的,但如果程序员与 C 无关,他们仍然必须根据抽象接口遵守 C-Fortran 互操作性的规则。
    • 另外,@francescalus 关于 2018 年的回答中的第二段是否正确?我自己实际上在标准中找不到它。
    • 是的,这对于 F2018 是正确的。 “CPTR 应该是 C_FUNPTR 类型的标量。它是一个 INTENT (IN) 参数。它的值应该是可互操作的过程的 C 地址,或者是从内部模块 ISO_C_BINDING 对函数 C_FUNLOC 的引用的结果。”而 F2008 说:“CPTR 应该是 C FUNPTR 类型的标量。它是一个 INTENT (IN) 参数。它的值应该是可互操作的过程的 C 地址。”
    猜你喜欢
    • 2011-02-11
    • 2013-12-31
    • 1970-01-01
    • 1970-01-01
    • 2012-04-15
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 2018-07-17
    相关资源
    最近更新 更多