Ctypes教程
- Python
- 19小时前
- 11热度
- 0评论
Ctypes教程
ctypes是一个可以让python调用C语言DLL接口(32位、64位)第三方python包
1 引入DLL
不同类型的C接口,需要使用不同的方式调用,不然会报以下错误
ValueError: Procedure probably called with too many arguments
# stdcall调用约定
xxx = ctypes.windll.LoadLibrary("dllpath")
xxx = ctypes.WinDLL("dllpath")
# cdecl调用约定(32位尽量用windll)
xxx = ctypes.cdll.LoadLibrary("dllpath")
xxx = ctypes.CDLL("dllpath") 备注:32位系统不能走C:\\Windows\\System32,只能用其他位置的,如下
ctypes.cdll.LoadLibrary("C:\\driver\\xxx.dll")2 常用类型
| Ctypes类型 | 对应C类型 | 对应Python类型 |
|---|---|---|
| c_char | char | 1个元素的str |
| c_byte | char | int/long |
| c_ubyte | 无符号char | int/long |
| c_int | int | int/long |
| c_long | long | int/long |
| c_float | float | float |
| c_double | double | float |
| c_bool | bool | bool |
| c_char_p | char* | 任意元素的str或None |
| c_void_p | void* | int/long或None |
基本类型的指针类型就在后面加_p就行
3 基本元素、数组、指针
# 基本元素
i = c_int(5) # 创建一个C的基本类型
print(i.value, type(i), type(i.value)) # 5, ctypes.c_long, int
s = c_char("a".encode("utf-8"))
print(s.value, type(s), type(s.value)) # a, ctypes.c_char, bytes# 指针
tmpStr = c_char_p(b"hello") # 等价于 "hello".encode("utf-8")
print(tmpStr.value, type(tmpStr), type(tmpStr.value)) # hello, ctypes.c_char_p, bytes# 数组
data = [1,2,3]
cIntArray = c_int * 3 # 创建一个数组类型(长度为3)
arr = cIntArray(*data) # 将python的data列表初始化到c数组
for a in arr:
print(a, type(a)) # 1, int
# 指针数组
data = [b"aa", b"bb", b"cc"]
cCharpArray = c_char_p * 3
arr = cCharpArray(*data)
for a in arr:
print(a,type(a)) # aa, bytes
# 数组指针
arr = (ctypes.c_int * 5)(1, 2) # 定义c_int数组
Apointer = ctypes.cast(arr, ctypes.POINTER(ctypes.c_int)) # 获取指向数组的指针
Apointer[0] = 10 # 通过指针修改数组中的值
Apointer[1] = 20
print(my_array[0].value, my_array[1].value) # 10 204 引用、指针
备注:使用byref速度会更快,因为无需创建指针,假设C的函数为getData,则以下两种写法等价,和C语言的指针与引用用法差不多
data = c_int(0) cdllPackage.getData(pointer(data)) # 两种等价 cdllPackage.getData(byref(data)) # 两种等价 print(data)
# 指针
data = c_int(0)
p = pointer(data) # 创建一个指向值为0的c_int元素
# 5 LP_c_long ctypes.c_long int
print(p.contents.value, type(p), type(p.contents), type(p.contents.value))
# 引用
print(byref(data))pointer和POINTER()的区别
pointer()用于将对象转化为指针、POINTER()用于定义某个类型的指针int_p1 = pointer(c_int(3)) # pointer直接指向对象 print(int_p1, int_p1.contents, int_p1[0]) int_p2 = POINTER(c_int) # POINTER(c_int)是一个指针类型 p = int_p2(c_int(3)) print(p, p.contents, p[0])
5 枚举类
- 直接换成枚举变量对应的类型即可
6 结构体
- 必须继承Structure,写一个
_fields_
from ctypes import *
class TagData(Structure):
_fields_ = [("value", c_double),
("time", c_long),
("charArray", c_char * 5) # char数组类型
]
cData = TagData(*[c_double(0.),
c_long(0),
"aaaaa".encode("utf-8") # 直接用字符串编码映射即可
]) # 初始化一个C的结构体对象(TagData类型)
# 结构体数组
tagDataList = [cData]
cTagDataList = (Tag * len(tagDataList))(*tagDataList) # 和其他的差不多
# 注意:结构体对象传入c++接口时,若c++需要修改参数,也需要将整个对象用byref包起来todo 嵌套结构体,后续有空加
备注
若报以下提示,可能是结构体某些元素过长,可以适当缩减一点数组类型的len,不一定非要严格按照C文档来
ValueError: Procedure probably called with too many arguments (288 bytes in excess)
7 调用C函数
- python会将C/C++函数的返回类型默认为int,所以数字类型无需指定restype
示例程序1(传char*值、接收值)
# 1. 加载dll rtdb = windll.LoadLibrary( 'RTDB.dll') # 2. 获取句柄 rtdb.rtdb_Create.restype = c_ulong # 设置返回类型为c_ulong(可不指定) rtdbHandle_ = rtdb.rtdb_Create(server_info, c_int(29)) # 29是int类型枚举 print(type(rtdbHandle_)) # <class 'int'> # 3. 将上一步的句柄放入函数获取值 msg = c_char_p(b"") # 创建一个char*指针 rtdb.rtdb_GetErrorEx.argtypes = [c_ulong, c_char_p] # 设置参数类型(必须) rtdb.rtdb_GetErrorEx(rtdbHandle_, errMsg) # 通过入参传值 print(errMsg, errMsg.value.decode(), type(errMsg)) # msg, failed, <class 'ctypes.c_char_p'>示例程序2
调用C函数、传递参数、接收返回值方法
# 实例讲解
"""
- op: dll对象
- op.op_get_multi_value: dll对象的方法
- resValue、resStatus:用于通过byref引用的方式接收op_get_multi_value传递的参数
- res: 返回值
"""
op = windll.LoadLibrary(cfg.sis_dll_path + 'opapi.dll')
resValue = c_float(0.) # 存储结果值
resStatus = c_short(0) # 存储结果状态
handler = op.op_new_group(c_int(len(points))) # 创建句柄
res = op.op_get_multi_value(handler, byref(resValue), byref(resStatus))