0%

Python使用ctypes模块调用DLL

Python使用ctypes模块调用DLL

注意

使用ctypes调用DLL文件时,要保证python解释器的位数和DLL使用的C解释器位数一致,32位C解释器必须与32位Python解释器配合使用,否则会报错OSError: [WinError xxx] xxx 不是有效的 Win32 应用程序

1
2
3
4
# 查看当前python环境使用的解释器位数
import platform

print(platform.architecture())

加载

针对DLL的两种调用约定,使用不同的调用方法

1
2
3
4
5
6
7
8
9
10
11
import ctypes

# cdecl
lib = ctypes.cdll.ctypes.cdll.LoadLibrary("dll_path")
# 或
lib = ctypes.CDLL("dll_path")

# stdcall
lib = ctypes.windll.LoadLibrary("dll_path")
# 或
lib = ctypes.WinDLL("dll_path")

调用DLL内方法

通过lib.methodName即可调用DLLn内部方法,调用DLL内部方法时,要将python参数通过ctypes转换为C语言参数

ctypes C类型与python类型对应表

ctypes type C type Python type
c_bool _Bool bool(1)
c_char char 单个字符的bytes对象,等同于 b’a’
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 or long long int
c_size_t size_t int
c_ssize_t ssize_t int
c_float float float
c_double double float
c_longdouble long double float
c_cahr_p char * bytes object or None
c_wchar_p wchar * string or None
c_void_p void * int or None

调用范例

Example1:

1
2
# DLL中定义的函数
int func(char *ip, int port);
1
2
3
4
# lib为调用CDLL或WinDLL返回的对象
ip = '192.168.0.2'
port = 8939
res = lib.func(ctypes.c_char_p(ip.encode()), ctypes.c_int(port))

Example2:

1
2
# DLL中定义的方法,方法内部会通过buf和data的指针,修改参数值向函数外传值
int func2(char *buf, int* data);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
data = ctypes.c_int()
# 创建一个string buffer, 指定容量为100
buf = ctypes.create_string_buffer(100)
# ctypes.byref获取buf的引用,也可以使用ctypes.pointer(buf)
res = lib.func2(ctypes.byref(buf), ctypes.byref(data))
# 获取data的值
print(data.value)
# buf是一个c的char类型的数组,需要自定义转换为Python的字符串
# print(buf) ==> <ctypes.c_char_Array_100 object at 0x02F2AF80>
print(buf.value.decode())
# 直接遍历buf
for i in range(len(buf)):
ch = buf[i].decode()
# 特殊字符处理
if ch != "\n" and ch != "\t" and ch != "\r" and ch != "\0":
ret_str = ret_str + ch
ret_str= ret_str.strip()ret_str.strip()

Note:

C语言字符串的末尾会带有‘\0’结束符, 直接处理字符串时要注意‘\0’

ctypes.byref用来传递引用参数,ctypes.pointer()作为传参会创建实际的指针对象

byref可以通过value属性获取值,pointer可以通过contents获取返回值

如果参数需要传递空指针时,直接传递None,用None作为空指针

Example3:

数组参数的创建

1
2
3
4
5
6
# arr1 = int[5] 并且值为0
arr1 = (ctypes.c_int * 5)()
# arr2 = double[10] 并且前三个值为1,2,3 其他值为0
arr2 = (ctypes.c_double)(1,2,3)
# 二维数组
arr3 = ((c_int * 4) * 3)()

Example4:

结构体传参, 结构体参数可以通过Python class创建

1
2
3
4
5
6
7
8
9
10
11
# _fileds_属性定义为二维tuple
# 二维tuple中的每个tuple 第一个值为变量名,第二个值为类型
class StructParam(types.Structure):
_fields_ = (
('x', c_int),
('y', c_double)
)

param = StructParam()
param.x = 10
param.y = 20

Note:

python中定义的结构体的class名,变量名可以不同于C中定义的结构体,但是变量的类型和定义顺序一定要与C中的类型顺序一致。

附录

ctypes中文文档