为何F2PY无法与静态库printtest.lib正确链接?
我是机械工程师,计算机背景有限,熟悉Python/C++但Fortran经验不多。现在尝试将调用第三方静态库printtest.lib的Fortran子程序转为Python扩展模块,其中INIT子程序调用库中的PRINTTEST子程序,但没有该库的源码。用f2py编译时出现LNK2001未解析外部符号printtest_的错误,尝试过Ctypes加载DLL也没成功。
复现代码
initialize.f90
SUBROUTINE INIT(A,N) INTEGER N REAL*8 A COMMON / FIXIN / NVAR1, NVAR2, ZVAR3 NVAR1 = 1 NVAR2 = 2 ZVAR3 = 0.65 A = N + 2 CALL PRINTTEST ! 该子程序定义在PRINTTEST.lib中 END SUBROUTINE INIT
PRINTTEST子程序示例(仅作说明,无源码)
SUBROUTINE PRINTTEST COMMON / FIXIN / NVAR1, NVAR2, ZVAR3 print *, 'NVAR1 = ', NVAR1 print *, 'NVAR2 = ', NVAR2 print *, 'ZVAR3 = ', ZVAR3 END SUBROUTINE PRINTTEST
使用的f2py编译命令(run_f2py.bat)
python -m numpy.f2py -c initialize.f90^ -L"%IFORT_COMPILER17%compiler\lib\intel64"^ -L"C:\Users\me\PyCharmProjects\FortranInterface"^ -l"printtest"^ -m fort_module^ --f90exec="C:\Users\me\IntelSWTools\compilers_and_libraries_2017.6.270\windows\bin\intel64\ifort.exe"^ --f90flags="/dll printtest.lib"^ --verbose
链接错误信息
initialize.o : error LNK2001: unresolved external symbol printtest_ .\fort_module.cp37-win_amd64.pyd : fatal error LNK1120: 1 unresolved externals
f2py传递给链接器的命令
C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\bin\HostX86\x64\link.exe^ /nologo^ /INCREMENTAL:NO^ /LTCG^ /DLL^ /MANIFEST:EMBED,ID=2^ /MANIFESTUAC:NO^ /LIBPATH:C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2017.6.270\windows\compiler\lib\intel64^ /LIBPATH:C:\Users\me\PyCharmProjects\FortranInterface^ /LIBPATH:C:\Users\me\Anaconda3\libs^ /LIBPATH:C:\Users\me\Anaconda3\PCbuild\amd64^ /LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\ATLMFC\lib\x64^ /LIBPATH:C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Tools\MSVC\14.16.27023\lib\x64^ /LIBPATH:C:\Program Files (x86)\Windows Kits\NETFXSDK\4.6.1\lib\um\x64^ /LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.17763.0\ucrt\x64^ /LIBPATH:C:\Program Files (x86)\Windows Kits\10\lib\10.0.17763.0\um\x64^ printtest.lib python37.lib^ /EXPORT:PyInit_fort_module^ C:\Users\me\AppData\Local\Temp\tmpoc9b4k88\Release\Users\me\AppData\Local\Temp\tmpoc9b4k88\src.win-amd64-3.7\fort_modulemodule.obj^ C:\Users\me\AppData\Local\Temp\tmpoc9b4k88\Release\Users\me\AppData\Local\Temp\tmpoc9b4k88\src.win-amd64-3.7\fortranobject.obj^ C:\Users\me\AppData\Local\Temp\tmpoc9b4k88\Release\initialize.o^ C:\Users\me\AppData\Local\Temp\tmpoc9b4k88\Release\Users\me\AppData\Local\Temp\tmpoc9b4k88\src.win-amd64-3.7\fort_module-f2pywrappers.o^ /OUT:.\fort_module.cp37-win_amd64.pyd^ /IMPLIB:C:\Users\me\AppData\Local\Temp\tmpoc9b4k88\Release\Users\me\AppData\Local\Temp\tmpoc9b4k88\src.win-amd64-3.7\fort_module.cp37-win_amd64.lib
环境详情
- Windows 10
- MS Visual Studio 2017
- Intel Visual Fortran 2017
- Anaconda 3(Python 3.7)
解决方法
1. 检查静态库的名称修饰规则
Fortran编译器会对子程序名做名称修饰,比如Intel Fortran默认将PRINTTEST转为小写加下划线的printtest_,但如果静态库是用其他编译器(如GFortran)编译的,修饰规则可能不同。用dumpbin工具查看库中的符号:
dumpbin /symbols printtest.lib
如果输出中没有printtest_,而是PRINTTEST或_printtest这类形式,需要在Fortran代码中用EXTERNAL声明指定符号名,或者调整编译选项。
2. 修正f2py编译选项
当前--f90flags="/dll printtest.lib"是错误的,/dll是链接器选项,不是编译器选项。正确的命令应该去掉该参数,确保-lprinttest正确指向库:
python -m numpy.f2py -c initialize.f90^ -L"%IFORT_COMPILER17%compiler\lib\intel64"^ -L"C:\Users\me\PyCharmProjects\FortranInterface"^ -lprinttest^ -m fort_module^ --f90exec="C:\Users\me\IntelSWTools\compilers_and_libraries_2017.6.270\windows\bin\intel64\ifort.exe"^ --verbose
同时确认-L指定的目录确实包含printtest.lib。
3. 确保编译架构一致
检查printtest.lib是32位还是64位,当前编译的是64位Python扩展(win_amd64),所以静态库必须也是64位。用dumpbin查看架构:
dumpbin /headers printtest.lib | findstr "machine"
输出为8664 machine (x64)才是64位,否则需要获取对应架构的库。
4. 显式声明外部子程序
在initialize.f90中显式声明PRINTTEST为外部子程序,避免编译器名称修饰歧义:
SUBROUTINE INIT(A,N) INTEGER N REAL*8 A EXTERNAL PRINTTEST ! 显式声明外部子程序 COMMON / FIXIN / NVAR1, NVAR2, ZVAR3 NVAR1 = 1 NVAR2 = 2 ZVAR3 = 0.65 A = N + 2 CALL PRINTTEST END SUBROUTINE INIT
5. 尝试先编译为DLL再用Ctypes加载
如果f2py方式仍有问题,先将initialize.f90和printtest.lib编译为DLL:
ifort /dll initialize.f90 printtest.lib /link /out:init.dll
然后用Ctypes加载,注意Fortran子程序的调用约定(默认是stdcall):
import ctypes # 加载DLL dll = ctypes.CDLL('./init.dll') # 查看导出的符号名,比如用dumpbin /exports init.dll init_func = dll['init_'] # 声明参数类型 init_func.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.c_int] init_func.restype = None # 调用 a = ctypes.c_double() n = ctypes.c_int(3) init_func(ctypes.byref(a), n) print(a.value)
内容的提问来源于stack exchange,提问作者LinharesAJ

