C回调函数的抽象Fortran接口是否需要bind(C)属性?

时间:2019-02-25 22:51:01

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

考虑以下Fortran代码,其中模块runFoo4C(...) bind(C, name="runFoo")中可与C互操作的子例程Foo_mod以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接口由上面代码中模块getLogFunc4C_proc()中的CallbackInterface_mod给出。现在的问题:

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

实际上,在抽象接口中对此bind(c)属性进行注释不会导致使用ifort(18.0.2 Windows编译器)引起的任何编译或运行时错误。

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

2 个答案:

答案 0 :(得分:2)

抽象接口中BIND(C)的存在(或不存在)改变了过程指针的特性,但是这样做的方式是该程序无法揭示的。因为您通过从C_FUNPTR转换而来的指针对getLogFunc进行了调用,所以可以防止编译器注意到不匹配,如果抽象接口中省略了BIND(C)。例如,如果该过程具有character(*)参数,则由于不匹配而会发生很多不好的事情。

只要您不说NAME =,BIND(C)本身就可以在抽象界面中使用。由于它会更改过程的调用方式,因此如果被调用过程是可互操作的,则必须指定它。

关于“如果不需要,那么在这个抽象接口中如何进行变量声明?它们是否需要通过iso_c_binding内部模块中的C兼容类型进行声明?”,您会遇到常见错误,即在内部函数中合并定义ISO_C_BINDING模块具有可互操作性。该模块中的种类常量只是数字,没有什么神奇的。您需要在类型,种类和等级(某些例外)中使实际参数和虚拟参数匹配。

答案 1 :(得分:1)

c_f_procpointer的规范在Fortran 2008和Fortran 2018中有所不同。无论如何,让我们来看一下语句

call c_f_procpointer(cptr=getLogFuncFromC, fptr=getLogFunc)

在Fortran 2008中,fptr参数的接口必须与cptr参数的目标可互操作。为了互操作,该接口必须具有bind属性。

在Fortran 2018下,放宽了此要求,但是仅当fptr参数是对{{1}的引用的结果时,才允许对cptr参数使用不可互操作的接口}。根据调用c_funloc的方式,有可能发生这种情况。

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