如何用Python ctypes调用Fortran编译的共享库?
我没有Fortran编程经验,尝试用Python ctypes调用Fortran编译的共享库。已经用命令gfortran -shared -g -o test.so test.f90将test.f90编译为test.so共享库。
查阅资料后知道要匹配对应数据类型(比如用ctypes.c_double(123)对应Fortran的real(kind=c_double)),但还是出现Type Error: wrong type错误。对于real(kind=c_double),dimension(*)类型的数组参数,我尝试用np.array([[1,2],[3,4]]).ctypes.data_as(POINTER(c_double))传递,又出现新错误:TypeError: item 2 in _argtypes_ has no from_param method。
附上Fortran代码与Python代码,不确定函数需要传递的正确数据类型,求解答。
Fortran代码(test.f90)
function sticksum( anzd, w, b, a, weight, val, maxTchan, sthresh) result(spek) bind(c, name="sticksum") use, intrinsic :: iso_c_binding real(kind=c_double) :: anzd real(kind=c_double) :: b,a real(kind=c_double),dimension(*) :: w,weight real(kind=c_double),dimension(*) :: val integer(kind=c_int) :: maxTchan real(kind=c_double) :: sthresh real(kind=c_double) :: spek integer :: i, t, j,anz real :: stick, maxw anz = anzd spek = 123 i=1 t = w(i) * b + a + 1.5 if(t >= 1) THEN spek = anz stick = 0. + val(t) maxw = weight(i)*stick do i=2,anz t = w(i) * b + a + 1.5 if(t > maxTchan) exit stick = val(t) maxw = max(weight(i)*stick,maxw) if( (w(i)*w(i)-w(i-1)*w(i-1)) > 0.5) THEN spek = spek + maxw maxw = 0 end if end do end if end function sticksum
原Python代码
from ctypes import * import numpy as np so_file = "./test.so" my_functions = CDLL(so_file) print(type(my_functions)) my_functions.sticksum_4.argtypes = [c_double,np.ndarray.ctypes,c_double,c_double,np.ndarray.ctypes,np.ndarray.ctypes,c_int, c_double] my_functions.restype = c_double anzd = c_double(123) w = np.array([[1,2],[3,4]]).ctypes.data_as(POINTER(c_double)) b=c_double(123) a=c_double(123) weight=np.array([[1,2],[3,4]]).ctypes.data_as(POINTER(c_double)) val=np.array([[1,2],[3,4]]).ctypes.data_as(POINTER(c_double)) maxTchan=c_int(123) sthresh=c_double(123) sum = my_functions.sticksum_4(anzd,w,b,a,weight,val,maxTchan,sthresh)
问题修正与解释
1. 函数名错误
Fortran代码中通过bind(c, name="sticksum")指定了C兼容的函数名为sticksum,Python中不需要加_4后缀,直接使用my_functions.sticksum即可。
2. argtypes类型错误
np.ndarray.ctypes不是有效的ctypes类型,Fortran的dimension(*)数组对应C的指针类型,所以argtypes里的数组参数应该用POINTER(c_double)。
3. 数组类型与内存布局
确保numpy数组是np.float64类型(对应c_double),可以在创建数组时指定dtype=np.float64;另外Fortran默认是列优先存储,如果你需要传递多维数组给Fortran处理,最好用np.asfortranarray()转换内存布局,不过这里Fortran用dimension(*)按一维处理,所以直接转指针也能工作,但显式指定类型更稳妥。
修正后的Python代码
from ctypes import * import numpy as np so_file = "./test.so" my_functions = CDLL(so_file) # 正确设置函数参数类型与返回值类型 my_functions.sticksum.argtypes = [ c_double, # anzd POINTER(c_double), # w c_double, # b c_double, # a POINTER(c_double), # weight POINTER(c_double), # val c_int, # maxTchan c_double # sthresh ] my_functions.sticksum.restype = c_double # 准备参数,确保数组为float64类型 anzd = c_double(123) # 创建float64类型的数组,转成c_double指针 w = np.array([[1,2],[3,4]], dtype=np.float64).ctypes.data_as(POINTER(c_double)) b = c_double(123) a = c_double(123) weight = np.array([[1,2],[3,4]], dtype=np.float64).ctypes.data_as(POINTER(c_double)) val = np.array([[1,2],[3,4]], dtype=np.float64).ctypes.data_as(POINTER(c_double)) maxTchan = c_int(123) sthresh = c_double(123) # 调用函数 sum_result = my_functions.sticksum(anzd, w, b, a, weight, val, maxTchan, sthresh) print(sum_result)
额外注意点
- Fortran中
anz = anzd是把双精度数转成整数,如果anzd不是整数可能会截断,这里要注意逻辑是否符合预期。 - 传递数组时,Fortran不会检查数组长度,需要确保Python传递的数组长度足够,避免越界访问。
内容的提问来源于stack exchange,提问作者Cosis94

