64位Windows10下使用ctypes调用C编译DLL函数失败问题咨询
我近期尝试将C代码编译为DLL后通过Python的ctypes加载调用,但始终未成功,运行环境为64位Windows10,原始C代码如下:
//Filename: my_functions.c # include <stdio.h> int square(int i){ return i*i }
我现将目前尝试过的两种方案总结如下:
方法1
首先我在命令提示符中使用cl.exe编译64位DLL,执行命令如下:
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools\VsDevCmd.bat" -arch=x64
cl.exe /D_USRDL /D_WINDLL my_functions.c /MT /link /DLL /OUT:my_functions.dll /MACHINE:X64
编译成功后得到my_functions.dll文件,随后我使用如下Python代码(Python 3.8,Spyder环境)尝试加载DLL:
# Filename: calling_c_functions.py from ctypes import * my_functions = CDLL("./my_functions.dll") print(type(my_functions)) print(my_functions.square(10)) print("Done")
但运行后报错:AttributeError: function 'square' not found。
我查询微软官方文档了解到,DLL文件包含导出表,记录所有对外暴露的函数信息,这些函数需要通过ordinal number(序号)或name(名称)访问,导出名称可能和原函数名square不同。因此我尝试了第二种方案。
方法2
参考Stack Overflow相关问题《Python: accessing DLL function using ctypes -- access by function name fails》的解决方案,我修改C代码添加__declspec(dllexport)关键字,内容如下:
//Filename: my_functionsDLL2.dll #include <stdio.h> extern "C" { __declspec(dllexport) int square(int i); } __declspec(dllexport) int square(int i) { return i * i; }
编译得到DLL后,我尝试用link.exe查看DLL的导出函数名,执行命令如下:
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\link.exe"
link /dump /exports my_functionsDLL2.dll
预期可以看到导出函数列表,但实际输出报LNK4048错误,提示文件格式无效被忽略,完整输出如下:
Microsoft (R) COFF/PE Dumper Version 14.29.30037.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file myfunctionsDLL2.dll myfunctionsDLL2.dll : warning LNK4048: Invalid format file; ignored Summary
烦请各位帮忙排查我的操作存在什么问题,谢谢。
问题排查与解决方案
你操作里的错误集中在以下4个点:
- 方法1的C代码没有添加函数导出声明,DLL导出表不会包含
square函数,Python调用找不到属于正常现象,同时编译命令里的宏写错:/D_USRDL应为/D_USRDLL,少打了末尾的L。 - 方法2的C代码混用了C语法:
extern "C"是C独有语法,如果你的源文件后缀是.c,C编译器会直接报错,如果你后缀用了.cpp才会生效,同时你注释里把C源文件名写为.dll后缀属于笔误,编译时注意核对源文件名。 - 查看导出表时报LNK4048是因为你调用了32位版本的
link.exe:路径C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\下的是32位工具链,无法识别64位的PE文件格式,自然会报格式无效。 - 原始C代码里
return i*i末尾少了分号,属于语法错误,编译时会报错,你说编译成功大概率是手敲代码时漏写了。
正确操作步骤
- 修正C代码,如果用C语言编译(后缀
.c),代码如下:
// 文件名:my_functions.c #include <stdio.h> __declspec(dllexport) int square(int i) { return i * i; }
如果用C++编译(后缀.cpp),需要加extern "C"避免名称修饰:
// 文件名:my_functions.cpp #include <stdio.h> extern "C" __declspec(dllexport) int square(int i) { return i * i; }
- 打开64位VS开发人员命令提示符,执行编译命令:
cl.exe /D_USRDLL /D_WINDLL my_functions.c /MT /link /DLL /OUT:my_functions.dll /MACHINE:X64
- 直接在当前命令提示符窗口执行导出表查看命令,不需要手动调用其他路径的link.exe,当前环境默认的64位工具链可以正常识别64位DLL:
link /dump /exports my_functions.dll
输出里可以看到导出的square函数名称。
4. Python调用前确认你的Python是64位版本,32位Python无法加载64位DLL,调用代码建议明确声明函数参数和返回值类型,避免隐式类型转换出错:
from ctypes import CDLL, c_int my_functions = CDLL("./my_functions.dll") # 声明参数类型为int my_functions.square.argtypes = [c_int] # 声明返回值类型为int my_functions.square.restype = c_int print(my_functions.square(10)) # 输出100即为调用成功
内容的提问来源于stack exchange,提问作者Ogiad

