如何用Python ctypes对接Fortran参数化派生类型?
问题:Python调用含参数化派生类型的Fortran库出现段错误
尝试用Python代码对接含参数化派生类型的Fortran库时,Python脚本触发段错误(Process finished with exit code 139 (interrupted by signal 11:SIGSEGV)),无任何输出。但Fortran代码编译为可执行程序运行正常。
最小复现代码
Python代码
import ctypes from pathlib import Path class Derived(ctypes.Structure): ... library = ctypes.CDLL(str(Path(__file__).parent / "test_lib.so")) n = 4 c_n = ctypes.c_int(n) Derived._fields_ = [ ("n", ctypes.c_int), ("y", ctypes.c_double * n) ] d = Derived() library.test_derived(ctypes.byref(c_n), ctypes.byref(d)) print(d.y[:])
Fortran代码(编译为test_lib.so)
module test_module use iso_c_binding, only: c_double, c_int implicit none type, bind(c) :: Derived(n) integer(c_int), len :: n real(c_double), dimension(n) :: y end type Derived contains subroutine test_derived(number, der) bind(c, name="test_derived") integer(c_int), intent(in) :: number type(Derived(n=number)), intent(out) :: der der%y = 2.d0 write(*, *) "Fortran:", der%n, der%y end subroutine test_derived end module test_module program main use test_module implicit none integer, parameter :: n_points_1 = 5 integer, parameter :: n_points_2 = 6 type(Derived(n=n_points_1)) :: d1 type(Derived(n=n_points_2)) :: d2 call test_derived(n_points_1, d1) call test_derived(n_points_2, d2) end program
问题原因
Fortran的**参数化派生类型(带len参数)**即便添加了bind(c),其内存布局也无法与C/Python的结构体兼容:
- Fortran编译器会为参数化类型生成额外的内部元数据,长度信息的存储位置、类型大小的计算逻辑和C结构体完全不同;
- 子routine中
type(Derived(n=number))的类型大小是运行时动态确定的,但ctypes的结构体大小是编译时静态确定的,两者内存布局不匹配直接导致内存越界,触发段错误。
Fortran可执行程序能正常运行是因为内部对参数化类型的处理是自洽的,所有类型信息由编译器统一管理,但跨语言调用时,C/Python无法识别Fortran参数化类型的内部结构。
解决方案
不要直接暴露参数化派生类型给C/Python,改用**C兼容的基础类型(整数、指针)**封装数据传递,具体步骤如下:
修改后的Fortran代码
module test_module use iso_c_binding, only: c_double, c_int, c_ptr, c_f_pointer, c_null_ptr implicit none contains subroutine test_derived(number, y_ptr) bind(c, name="test_derived") integer(c_int), intent(in) :: number type(c_ptr), intent(out) :: y_ptr real(c_double), dimension(:), pointer :: y ! 动态分配数组并赋值 allocate(y(number)) y = 2.d0 ! 将Fortran指针转换为C兼容指针 y_ptr = c_loc(y) write(*, *) "Fortran:", number, y end subroutine test_derived ! 新增内存释放接口,避免内存泄漏 subroutine free_derived(y_ptr) bind(c, name="free_derived") type(c_ptr), intent(inout) :: y_ptr real(c_double), dimension(:), pointer :: y call c_f_pointer(y_ptr, y) deallocate(y) y_ptr = c_null_ptr end subroutine free_derived end module test_module
修改后的Python代码
import ctypes from pathlib import Path library = ctypes.CDLL(str(Path(__file__).parent / "test_lib.so")) # 显式声明函数参数类型和返回值类型 library.test_derived.argtypes = [ctypes.c_int, ctypes.POINTER(ctypes.c_void_p)] library.test_derived.restype = None library.free_derived.argtypes = [ctypes.c_void_p] library.free_derived.restype = None n = 4 c_n = ctypes.c_int(n) y_ptr = ctypes.c_void_p() # 调用Fortran子routine library.test_derived(c_n, ctypes.byref(y_ptr)) # 将C指针转换为Python可访问的数组 y_array = (ctypes.c_double * n).from_address(ctypes.cast(y_ptr, ctypes.c_void_p).value) print("Python:", y_array[:]) # 释放Fortran分配的内存,避免泄漏 library.free_derived(y_ptr)
编译与运行
用gfortran编译Fortran代码为共享库:
gfortran -shared -fPIC test_module.f90 -o test_lib.so
运行Python脚本即可正常输出Fortran和Python的打印内容。
内容的提问来源于stack exchange,提问作者Beak
相关产品推荐
相关产品推荐

