Cython、C 和 Fortran
我想请教一下关于如何通过C语言调用Fortran函数的问题。这些C语言的函数会通过Cython在Python代码中使用。简单来说,我的流程是这样的:
Cython模块 -> C函数 -> Fortran函数,其中 -> 表示“调用”。
目前我已经成功从Cython调用了C函数,但在调用Fortran函数时遇到了困难。你能帮我吗?(如果能给个简单的例子就更好了)。
提前谢谢你。
补充说明:我使用的是gcc 4.1.2和gfortran。
3 个回答
有一个叫做 fwrap 的自动化工具,它可以为 Fortran 程序生成 C、Cython 和 Python 的接口。虽然我觉得它现在还处于测试阶段,但你可能会觉得它很有用,链接在 这里。
由于这个方法对调试高性能计算(HPC)代码很有用,这里有一个简单的“你好,世界”程序,使用Fortran编写,并通过Python调用。
使用的是 GNU Fortran (GCC) 9.3.0
和 Python 3.7.3
你至少需要:
- 一个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是不区分大小写的:这个块中的所有内容都应该用小写字母。
- 一个
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
编译这个。
- 一个标准的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链接方法的问题,可以在评论中询问。
第一个回答中的链接提到了一些过时的方法。现在,从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手册或其他地方了解可用的类型。