You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 23:30:54