ctypes库介绍
- python调用C++编译生成的动态链接库,包括DLL或者.so,需要用到ctypes库的方法
ctypes
是一个用于Python的外部函数库,它提供C兼容的数据类型,并允许在DLL或共享库中调用函数。
python调用的windows 的DLL
-
在python中ctypes只能调用C函数接口以及参数,不能直接传输和调用c++中的数据类型,如
string
类型;因此,在导出的头文件中,需指定C编译器来编译,且输入输出参数类型必须是C语言所支持的,因此c++头文件中导出函数名前面要加关键字extern "C" { }
-
ctype库函数接口支持的类型
ctypes 类型 C 类型 Python 类型 c_bool
_Bool
bool (1) c_char
char
单字符字节对象 c_wchar
wchar_t
单字符字符串 c_byte
char
int c_ubyte
unsigned char
int c_short
short
int c_ushort
unsigned short
int c_int
int
int c_uint
unsigned int
int c_long
long
int c_ulong
unsigned long
int c_longlong
__int64
或long long
int c_ulonglong
unsigned __int64
或unsigned long long
int c_size_t
size_t
int c_ssize_t
ssize_t
或Py_ssize_t
int c_float
float
float c_double
double
float c_longdouble
long double
float c_char_p
char *
(以 NUL 结尾)字节串对象或 None
c_wchar_p
wchar_t *
(以 NUL 结尾)字符串或 None
c_void_p
void *
int 或 None
基础类型之间的传参,int、float、char*
-
Python 默认函数的输入和返回值类型为 int 型,如果C的函数接口导出函数的输入和返回值类型不是int型,就需要
argtypes
和restype
指定函数形参和返回类型 -
如输入参数有多种类型,输出为int型),其接口头文件如下xxx.h,输入为2个char* 和2个int以及一个int*,返回int,其中1个char*和int*作为c++处理后的结果返回给python,类似java的jni层作用
#pragma once #include <string.h> #ifdef _WIN32 #ifdef _WINDLL #define DLL_EXPORT __declspec(dllexport) #else #define DLL_EXPORT __declspec(dllimport) #endif #else #define DLL_EXPORT #endif extern "C" { DLL_EXPORT int sum( int a, int b); DLL_EXPORT int str_add_sum(const char* p0, int a, int b, int *c, char* result); }
-
python调用接口示例,建议使用python3,python2可能会出现解码报错
from ctypes import * # pDLL = WinDLL("./Test.dll") ## for windows xxx.dll # pSo = cdll.LoadLibrary("./xxx.so") ## for linux xxx.so pDll = CDLL("./Test.dll") ## 均可 ## case 1: 接口函数输出输入均为int型, 无需指定参数 #最简单的形式 res = pDll.sum(1,2) print(res) ## case 2: 指定参数类型,对应上面头文件的类型 # str_add_sum接口调用示例 str_input = "1010" ##根据上述的ctype库函数接口支持表格,用argtypes指定函数的输入参数类型 pDll.str_add_sum.argtypes = [c_char_p, c_int, c_int, c_void_p, c_char_p] # c_void_p 对应接受int* # Use b'xxxxx' for byte strings that are translated to char* arg1 = c_char_p(str_input.encode('utf-8')) arg2 = 2 arg3 = 3 buftype = ctypes.c_int * 1 arg4 = buftype() arg5 = create_string_buffer(64) # 定义接受字节数的长度为64 ## restype指定接口的返回类型 pDll.str_add_sum.restype = c_int ##call api res = pDll.str_add_sum(arg1, arg2, arg3, arg4,arg5) # type is bytes #返回的是utf-8编码的数据,需要解码 print(arg4[0]) # 2+3=5 print(arg5.value.decode("utf-8")) print(res)
复杂类型的传参
- 接口如何实现复杂类型参数的传递?如字符串指针,一维数组,结构体等
- 字符串指针参数传递示例代码
# 通过byte字节类型创建内存空间,可避免内存泄漏问题 import ctypes from ctypes import * libc = CDLL("./detect.so") def detect_api(input_path, model): libc.wm_detect_api.argtypes = [c_char_p, c_char_p, c_float, c_char_p] # 输入参数 arg1 = c_char_p(input_path.encode('utf-8')) # 输入参数 arg2 = c_char_p(input_model.encode('utf-8')) arg3 = 0.25 # 输出返回参数 # 512可根据返回的字节数进行调整 resArray = create_string_buffer(512) libc.wm_detect_api.restype = c_int res = libc.wm_detect_api(arg1, arg2, arg3 , resArray) # resline is c_char_p object , so call value method convert to bytes type # type is bytes , using utf-8 keys parameters decode print(resArray.value.decode("utf-8")) return res
- 字节流数组传参,示例代码如下
# 函数encodeBitsBchApi前3个参数均为输入参数,最后一个参数为输出返回参数,类型为unsigned char*, 字节类型 def BCHEncode(input_stream, m, t): libc.encodeBitsBchApi.argtypes = [c_char_p, c_int, c_int , c_void_p] arg1 = c_char_p(input_stream.encode('utf-8')) arg2 = m arg3 = t # arg4 = create_string_buffer(128) 该方式也可用 buftype = c_ubyte * 128 # 定义字节类型变量 arg4 = buftype() code = libc.encodeBitsBchApi(arg1, arg2, arg3, arg4) # result = arg4.value bits = [] for i in range(64): bits.append(str(arg4[i])) return code, "".join(bits)
- 一维数组传参,示例代码如下
#coding=utf-8 import os import sys import ctypes from ctypes import * libc = cdll.LoadLibrary(os.getcwd() + "/imgPropreccess.so") # int imgSiftMatch(const char* inputMatchPath, const char* refSourcePath, float* points); def img_sift_match(inputPath, referencePath): # c接口的基础数据类型指针,在python指定类型时,可用v_void_p表示 libc.imgSiftMatch.argtypes = [c_char_p, c_char_p, c_void_p] # p = create_string_buffer(" ", 128) arg1 = c_char_p(inputPath.encode('utf-8')) arg2 = c_char_p(referencePath.encode('utf-8')) # 返回参数 float_array = c_float * 8 float_data = float_array() # convert to class '__main__.c_float_Array_8' # c_float_Array_8对象接受c接口返回的浮点型数组各个值 code = libc.imgSiftMatch(arg1, arg2, byref(float_data))#返回的是utf-8编码的数据,需要解码 points_list = [] # c_float_Array_8转换为列表返回 for i in range(8): points_list.append(int(0.5+float_data[i])) return code, points_list
- 结构体类型传参,示例代码如下
class Infos(ctypes.Structure): _fields_ = [ ('appid', c_char * 32), ('uuid', c_char * 128), ('num', c_int ), ('taskId', c_int), ] def pdf_extract_api(infile, licenseCode, salt, pAppid, traceId): # int licenseExtractPdfWmApi(const char* inputPath, const char * licenseCode, const char * salt, const char * appIds, int traceId, ExtractInfo *res); lib_embed_so.licenseExtractPdfWmApi.argtypes = [c_char_p, c_char_p, c_char_p, c_char_p, c_int, c_void_p] arg1 = c_char_p(infile.encode('gbk')) arg2 = c_char_p(licenseCode.encode('utf-8')) arg3 = c_char_p(salt.encode('utf-8')) arg4 = c_char_p(pAppid.encode('utf-8')) arg5 = traceId buftype = Infos * 1 result = buftype() res = lib_embed_so.licenseExtractPdfWmApi(arg1, arg2, arg3, arg4, arg5, result) print(res) print(result[0].appid, result[0].uuid, result[0].num, result[0].taskId)
windows常见的报错
- 通过ctypes.cdll.LoadLibrary可以调用指定路径下的dll,但如果被调用的dll又依赖于其他的dll,系统路径或当前路径搜索不到所依赖的,则会报错,OSError: [WinError 126] 找不到指定的模块,或报其他的溢出错误码
- 解决办法:利用VS 自带的window依赖可执行程序查看,然后查看所依赖的dll是否在系统变量路径中,可以加入系统路径或者python脚本当前目录
- 切换至路径
D:\Program Files (x86)\Microsoft Visual Studio 14.0\VC
- 执行
dumpbin /dependents xxx.dll
,xxx.dll换成绝对路径
- 切换至路径
- 若依赖的dll存在多个不同目录中,且依然报上述错误,则有可能是依赖的dll出现多个,系统反而不知道找哪个,删除其中一个变量的路径即可
- 解决办法:利用VS 自带的window依赖可执行程序查看,然后查看所依赖的dll是否在系统变量路径中,可以加入系统路径或者python脚本当前目录