通过 ctypes 在 Fortran 中的 python 回调

考虑这个 C 互操作的 Fortran 子例程,它从 Python 调用并将 Python 回调函数作为输入参数,然后调用它,


module FortranFunc_mod


    ! C-interoperable interface for the python callback

    abstract interface

        function getSquare_proc( x ) result(xSquared) bind(C)

            use, intrinsic :: iso_c_binding, only: c_double

            real(c_double), intent(in)              :: x

            real(c_double)                          :: xSquared

        end function getSquare_proc

    end interface


contains


    subroutine fortranFunc( getSquareFromPython ) bind(C, name="fortranFunc")

        !DEC$ ATTRIBUTES DLLEXPORT :: fortranFunc

        use, intrinsic :: iso_c_binding, only: c_funptr, c_f_procpointer, c_double

        implicit none

        type(c_funptr), intent(in)          :: getSquareFromPython

        procedure(getSquare_proc), pointer  :: getSquare

        real(c_double)                      :: x = 2._c_double, xSquared


        ! associate the input C procedure pointer to a Fortran procedure pointer

        call c_f_procpointer(cptr=getSquareFromPython, fptr=getSquare)

        xSquared = getSquare(x)

        write(*,*) "xSquared = ", xSquared

    end subroutine fortranFunc


end module FortranFunc_mod

Python 函数可能如下所示,


import numpy as np

import ctypes as ct


# import dll and define result type

ff = ct.CDLL('FortranFunc_mod')

ff.fortranFunc.restype = None


# define and decorate Python callback with propoer ctypes

@ct.CFUNCTYPE( ct.c_double, ct.c_double ) # result type, argument type

def getSquareFromPython(x): return np.double(x**2)


# call Fortran function

ff.fortranFunc( getSquareFromPython )

但是,使用 ifort 编译此代码(已成功完成)然后运行 Python 代码会导致以下错误,

在这个简单的例子中我错过了什么?Fortran 和 python 代码之间是否需要额外的 C-wrapper 来定义回调原型?如果您还可以提供 C 等效代码来调用 Python 函数,那也会有所帮助。


三国纷争
浏览 170回答 1
1回答

慕妹3146593

您的示例的主要问题是ff.fortranFunc只指定了它的返回类型,而不是它的参数类型。Fortran 子例程fortranFunc有一个输入参数,type(c_funptr)这也应该反映在 Python 端。究竟如何实现解决方案,取决于您是否只想在 Python 中进行更改,或者也愿意在 Fortran 源代码中进行更改。我将概述这两种解决方案:仅在 Python 中进行更改以下是 Python 测试例程的更新版本(我称之为test.py),具有以下特定更改:指定ff.fortranFunc.argtypesthearg_type被指定为指向 a 的指针c_double- 如何在 C 中传递标量参数回调函数getSquareFromPython也被修改以反映这一点x[0](有关最后两点的详细信息,请参阅ctypes 文档- 2.7 版本可能更清楚地解释了这一点)import ctypes as ct# callback function ctypes specificationreturn_type = ct.c_doublearg_type = ct.POINTER(ct.c_double)func_spec = ct.CFUNCTYPE(return_type, arg_type)# import dll and define result AND argument typeff = ct.CDLL('FortranFunc_mod')ff.fortranFunc.restype = Noneff.fortranFunc.argtypes = [ct.POINTER(func_spec),]# decorate Python callback@func_specdef getSquareFromPython(x):    return x[0]**2# call Fortran functionff.fortranFunc( getSquareFromPython )在 Python 和 Fortran 中进行更改如果您希望更接近原始 Python 实现,也可以通过对以下内容进行以下更改来实现test.py:changefortranFunc的参数类型:arg_type = ct.c_double改变getSquareFromPython的返回值:x**2但是,由于回调函数现在需要一个作为输入参数(而不是指向一个的指针),因此您必须通过将属性添加到虚拟参数c_double来更改 Fortran 抽象接口以反映这一点:valuexabstract interface    function getSquare_proc( x ) result(xSquared) bind(C)        use, intrinsic :: iso_c_binding, only: c_double        real(c_double), intent(in), value       :: x        real(c_double)                          :: xSquared    end function getSquare_procend interface编译运行编译并运行代码的任一修改版本,在 Windows 上使用 ifort 给我以下结果(适当更改编译命令和库名称,它也适用于 Linux 和 OS X 上的 gfortran):> ifort /DLL FortranFunc_mod.f90 /o FortranFunc_mod.dll...> python test.py xSquared =    4.00000000000000注意两种情况的区别getSquareFromPython通过查看' 参数的动态类型可以清楚地反映两种实现之间的差异x(它还解释了两种替代方案所需的符号更改)。对于提出的第一个替代方案,您可以将左侧的语句添加到getSquareFromPython,以获得右侧显示的结果:print(type(x).__name__)                 :  LP_c_doubleprint(type(x.contents).__name__)        :  c_doubleprint(type(x.contents.value).__name__)  :  floatprint(type(x[0]).__name__)              :  floatprint(x.contents.value == x[0])         :  True而对于第二种选择:print(type(x).__name__)                 :  float
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python