C 回调函数的抽象 Fortran 接口是否需要 bind(C) 属性?
Does the abstract Fortran interface of a C callback function require bind(C) attribute?
考虑以下 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 的种类声明?
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
。有可能发生这种情况,具体取决于 runFoo4C
的调用方式。
在任何一种情况下,都不需要编译器来诊断任何违反要求的情况(在最后一种情况下,很容易看出这有多么棘手)。
抽象接口中 BIND(C) 的存在(或不存在)改变了过程指针的特征,但以本程序未揭示的方式进行。因为您通过从 C_FUNPTR 转换而来的指针调用 getLogFunc,所以如果在抽象接口中省略 BIND(C),您可以防止编译器注意到不匹配。例如,如果过程有一个字符 (*) 参数,那么不匹配会发生很多不好的事情。
BIND(C) 本身在抽象接口中是可以的,只要您不也说 NAME=。由于它改变了过程的调用方式,如果被调用的过程是可互操作的,则必须指定它。
关于 "If it is not needed, then how about variable declarations in this abstract interface? Do they need to be declared by C-conforming kinds from the iso_c_binding intrinsic module?",您犯了一个常见错误,即混淆内部模块 ISO_C_BINDING 中的定义与可互操作。该模块中的种类常量只是数字,它们没有什么神奇之处。您需要使实际参数和虚拟参数在类型、种类和级别上匹配(有一些例外。)
考虑以下 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 的种类声明?
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
。有可能发生这种情况,具体取决于 runFoo4C
的调用方式。
在任何一种情况下,都不需要编译器来诊断任何违反要求的情况(在最后一种情况下,很容易看出这有多么棘手)。
抽象接口中 BIND(C) 的存在(或不存在)改变了过程指针的特征,但以本程序未揭示的方式进行。因为您通过从 C_FUNPTR 转换而来的指针调用 getLogFunc,所以如果在抽象接口中省略 BIND(C),您可以防止编译器注意到不匹配。例如,如果过程有一个字符 (*) 参数,那么不匹配会发生很多不好的事情。
BIND(C) 本身在抽象接口中是可以的,只要您不也说 NAME=。由于它改变了过程的调用方式,如果被调用的过程是可互操作的,则必须指定它。
关于 "If it is not needed, then how about variable declarations in this abstract interface? Do they need to be declared by C-conforming kinds from the iso_c_binding intrinsic module?",您犯了一个常见错误,即混淆内部模块 ISO_C_BINDING 中的定义与可互操作。该模块中的种类常量只是数字,它们没有什么神奇之处。您需要使实际参数和虚拟参数在类型、种类和级别上匹配(有一些例外。)