Ctypes教程

Ctypes教程

参考文献 https://zhuanlan.zhihu.com/p/145165873

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_charchar1个元素的str
c_bytecharint/long
c_ubyte无符号charint/long
c_intintint/long
c_longlongint/long
c_floatfloatfloat
c_doubledoublefloat
c_boolboolbool
c_char_pchar*任意元素的str或None
c_void_pvoid*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 20

4 引用、指针

备注:使用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))