Cython、C 和 Fortran

4 投票
3 回答
1418 浏览
提问于 2025-04-16 08:13

我想请教一下关于如何通过C语言调用Fortran函数的问题。这些C语言的函数会通过Cython在Python代码中使用。简单来说,我的流程是这样的:

Cython模块 -> C函数 -> Fortran函数,其中 -> 表示“调用”。

目前我已经成功从Cython调用了C函数,但在调用Fortran函数时遇到了困难。你能帮我吗?(如果能给个简单的例子就更好了)。

提前谢谢你。

补充说明:我使用的是gcc 4.1.2和gfortran。

3 个回答

1

有一个叫做 fwrap 的自动化工具,它可以为 Fortran 程序生成 C、Cython 和 Python 的接口。虽然我觉得它现在还处于测试阶段,但你可能会觉得它很有用,链接在 这里

2

由于这个方法对调试高性能计算(HPC)代码很有用,这里有一个简单的“你好,世界”程序,使用Fortran编写,并通过Python调用。

使用的是 GNU Fortran (GCC) 9.3.0Python 3.7.3

你至少需要:

  1. 一个Fortran代码,里面要有C语言的绑定(也就是说,每个子程序/函数前面要加上 bind(C),每个读写的变量要用 iso_c_binding)。我们把这个文件叫做 hello_fortran.f90
subroutine hello_fortran() bind(c)

    print *, "Hello world from fortran"

end subroutine hello_fortran

如果你不想在原来的Fortran代码中添加iso_c_bindings,你也可以在Fortran中写一个简单的包装函数,直接调用原来的代码。

使用 -c 选项编译这个代码,这样可以不链接,使用 -fPIC 选项可以生成位置无关的代码。 gfortran hello_fortran.f90 -c -fPIC -o hello_fortran.o 你可以直接在你的setup.py中链接这些目标文件,但我觉得把所有东西打包成一个共享库更简单 gfortran *.o -shared -o libhello_fortran.so 然后用 export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(pwd) 将当前工作目录添加到PATH中。

2. 一个Cython模块,这样可以从Python调用C函数(另外,你也可以使用CFFI,方法类似)。在我们的例子中,我们把这个文件叫做“hello_cython.pyx”。

cdef extern:
    void hello_fortran()

def hello_cython():
    print("Called hello_cython")
    hello_fortran()

关键是,对于每个你想调用的Fortran函数,你需要在 cdef extern 块中声明C接口。记住,Fortran是不区分大小写的:这个块中的所有内容都应该用小写字母。

  1. 一个 setup.py 文件,用来“编译”Cython模块。不幸的是,这个文件可能会变得非常复杂,所以下面给出的只是一个最简单的工作示例。
from distutils.core import setup, Extension
from Cython.Build import cythonize
import numpy

files = ['hello_cython.pyx']

ext_module = Extension(
    name = "hello_cython_FI",
    sources = files,
    include_dirs = ['.'],
    library_dirs=['.'],
    libraries=["hello_fortran"]
    )

setup(
    name = "hello_cython_FI",
    ext_modules = cythonize(ext_module)
)

需要注意的重要事项是,name 将给出Python中模块的名称,files 必须包含 .pyx 文件,libraries 必须写共享库的名称(也就是说,对于 libhello_fortran.so,写 hello_fortran)。

python setup.py build_ext --inplace 编译这个。

  1. 一个标准的Python脚本 hello_python.py,用来调用 hello_cython_FI。
import hello_cython_FI

print("Called hello_python")
hello_cython_FI.hello_cython()

然后你应该能得到:

>>> python hello_python.py
Called hello_python
Called hello_cython
Hello world from fortran

如果有关于Intel、OpenMP和OpenMP链接方法的问题,可以在评论中询问。

7

第一个回答中的链接提到了一些过时的方法。现在,从C语言调用Fortran,或者从Fortran调用C语言变得简单多了,因为Fortran增加了ISO C绑定这个功能。这个功能告诉Fortran编译器生成与C语言兼容的可执行代码。这样,程序员就不需要“破解”两者之间的连接,因为这个功能是语言的一部分,所以它在编译器和平台上都是独立的。技术上来说,ISO C绑定是Fortran 2003的一部分,但在许多编译器中已经使用了好几年,比如gfortran从4.3版本开始就支持这个功能,还有Intel的ifort。

如果你想从C语言调用Fortran的子程序或函数,你需要在声明Fortran的子程序或函数时使用bind C选项,并且在参数的声明中使用绑定提供的与C兼容的类型。gfortran手册中有“混合语言编程”的例子。由于ISO C绑定是语言的一部分,所以手册中的这一部分大多数情况下与编译器无关。在Stack Overflow的其他回答和网上也有其他例子。

下面是一个简单的代码片段(未经测试),展示了一个可以从C调用的Fortran子程序的声明:

subroutine test ( varint1, varflt2 )  bind ( C, name="MyTest" )

   use iso_c_binding

   integer (kind=c_int32_t), intent (in) :: varint1
   real (kind=c_float), intent (out) :: varflt2

这里的bind C名称"MyTest"会覆盖Fortran的名称——它是区分大小写的,这点和Fortran不同。也不用担心下划线的问题!变量类型应该很明显……可以查看gfortran手册或其他地方了解可用的类型。

撰写回答