1. 总述
开发者有三种方法可以在自己的Python代码中来调用C编写的函数: ctypes,SWIG,Python/C API,每种方式也都有各自的利弊。
这篇文章就来讨论一下使用 ctypes 在python中调用C代码的方法。ctypes 方法调用C代码,是使用到动态链接库( .so 或 .dll )的方式调用接口。
2. 动态链接库的生成
编写dll时,有个重要的问题需要解决,那就是函数重命名——Name-Mangling。解决方式有两种,一种是直接在代码里解决采用extent”c”、_declspec(dllexport)、#pragma comment(linker, “/export:[Exports Name]=[Mangling Name]”),另一种是采用def文件。
2.1 函数重命名
因为C和C++的重命名规则是不一样的。这种重命名称为”Name-Mangling”.
可能不同版本的编译器他们的Name-Mangling规则也是不同的。这样的话,不同编译器编译出来的目标文件.obj 是不通用的,因为同一个函数,使用不同的Name-Mangling在obj文件中就会有不同的名字。如果DLL里的函数重命名规则跟DLL的使用者采用的重命名规则不一致,那就会找不到这个函数.
C标准规定了C语言Name-Mangling的规范。这样就使得,任何一个支持C语言的编译器,它编译出来的obj文件可以共享,链接成可执行文件。这是一种标准,如果DLL跟其使用者都采用这种约定,那么就可以解决函数重命名规则不一致导致的错误.
影响符号名的除了C++和C的区别、编译器的区别之外,还要考虑调用约定导致的Name Mangling。
- extern “c” __stdcall的调用方式就会在原来函数名上加上写表示参数的符号;
- extern “c” __cdecl则不会附加额外的符号。
动态链接库的显式装入就是通过GetProcAddress函数,依据动态链接库句柄和函数名,获取函数地址。因为GetProcAddress仅是操作系统相关,可能会操作各种各样的编译器产生的dll,它的参数里的函数名是原原本本的函数名,没有任何修饰,所以一般情况下需要确保dll里的函数名是原始的函数名。
为达到该目的,可以采用以下方法:
- 如果源文件对外开放的函数使用了
extern "C" _cdecl修饰,那么就不需要再重命名了,这个时候dll里的名字就是原始名字 - 如果源文件对外开放的函数使用了
extern "C" _stdcall修饰,这时候dll中的函数名被修饰了,就需要重命名。重命名的方式有两种:- 使用*.def文件,在文件外为对外开放函数命别名;
- 使用#pragma,在源代码中为对外开放函数命别名。
2.2 显示对外开放函数
显示对外开放的函数,即可以理解为在.h中声明的函数,显示表示对外开放。
因DLL为单独对外开放的二进制文件,不包含.h,故需要在编译时显示表明对外开放的函数接口。
方法有二:
- 在源代码函数的定义或声明处,使用
__declspec(dllexport);
extern "C" __declspec(dllexport) int sum(int, int);
- 使用def文件来说明哪些函数用于导出,同时def文件里边还有函数的编号。
2.3 动态链接库的生成
以下以一个例子,进行解析如何使用 _cdecl(默认) 的方式修饰
// dll_test.h#ifndef _DLL_TEST_H#define _DLL_TEST_H#define DLLEXPORT extern "C" __declspec(dllexport)DLLEXPORT int add_int(int, int);DLLEXPORT float add_float(float, float);#endif
// dll_test.cpp#include <stdio.h>#include "dll_test.h"int add_int(int num1, int num2){return num1 + num2;}float add_float(float num1, float num2){return num1 + num2;
2.3.1 Xnix下.so的生成
$ gcc -shared -Wl,-soname,adder -o adder.so -fPIC dll_test.cpp
2.3.2 Windows下.dll的生成
VS下,设置 工程->属性->配置属性->常规->配置类型 为 动态库 。
查看生成的dll的函数符号:
a. 寻找 vcvars64.bat 文件,在cmd中执行该bat脚本;

b. 继续在命令行执行 dumpbin /exports xxx.dll:
> dumpbin /exports dll_test.dllDump of file dll_test.dllFile Type: DLLSection contains the following exports for dll_test.dll00000000 characteristicsFFFFFFFF time date stamp0.00 version1 ordinal base1 number of functions1 number of namesordinal hint RVA name1 0 00001019 add_int = @ILT+20(add_int)2 1 000011A9 add_float = @ILT+420(add_float)Summary1000 .00cfg3000 .data1000 .idata1000 .pdata3000 .rdata1000 .reloc1000 .rsrc9000 .text
注意:调用方应用所使用的系统位数(32-bit/64-bit)与编译出的动态链接库的系统位数应一致
3. python使用ctypes调用动态链接库
以调用 DLL 库为例
import ctypes# 加载动态链接库dll = ctypes.cdll.LoadLibrary('./dll_test.dll')# 设置调用接口入参类型dll.add_int.argtypes=[ctypes.c_int32, ctypes.c_int32]dll.add_float.argtypes=[ctypes.c_float, ctypes.c_float]# 设置调用接口返回值类型dll.add_int.restype=ctypes.c_int32dll.add_float.restype=ctypes.c_float# 调用接口dll.add_int(1, 2)dll.add_float(1.2, 2.3)
其余传参技巧见:https://www.cnblogs.com/TQCAI/p/8881530.html

