前段时间接到了一个需求是给一个蓝牙的SDK测试接口的稳定性,将SDK的接口文档给你了,需要每个接口都写一个对应的测试用例,SDK 是用c写的,而我python用的比较熟练些,所有记录下在ctypes库的使用方法。
1 python和c中类型映射
ctypes中数据类型
ctypes 类型 |
C 类型 |
Python 数据类型 |
c_bool |
_Bool |
bool (1) |
c_char |
char |
单字符字节串对象 |
c_wchar |
wchar_t |
单字符字符串 |
c_byte |
char |
int |
c_ubyte |
unsigned char |
int |
POINTER(c_ubyte) |
uchar* |
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 terminated) |
字节串对象或 None |
c_wchar_p |
wchar_t * (NUL terminated) |
字符串或 None |
c_void_p |
void * |
int 或 None |
2 加载共享库
1
2
3
4
|
import ctypes
# 加载本地的共享库,路径根据实际情况调整
lib = ctypes.CDLL('./libexample.so') # Linux/macOS平台
# lib = ctypes.WinDLL('example.dll') # Windows平台
|
3 调用函数
1
2
3
4
5
6
7
|
# 设置参数类型
lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
# 设置返回类型
lib.add.restype = ctypes.c_int
# 调用C函数
result = lib.add(2, 3)
print(result) # 输出:5
|
4 操作指针
在ctypes中,你可以使用pointer和byref来操作指针。
1
2
3
4
5
6
7
8
9
10
11
12
|
# 创建一个整数数组
arr = (ctypes.c_int * 5)(1, 2, 3, 4, 5)
# 获取指向数组首元素的指针
ptr = ctypes.pointer(arr)
# 通过指针访问数组元素
print(ptr[0]) # 输出:1
# 使用byref创建指向变量的指针
x = ctypes.c_int(10)
px = ctypes.byref(x)
# 通过指针修改变量的值
lib.increment(px) # 假设有一个increment函数用于增加整数的值
print(x.value) # 输出:11
|
1
2
3
4
5
6
7
8
9
10
|
# 调用系统的库函数测试
from ctypes import c_int, c_float, create_string_buffer, CDLL, byref
c_lib = CDLL('/lib/x86_64-linux-gnu/libc.so.6')
i = c_int()
f = c_float()
s = create_string_buffer(b"\000" * 32)
print(i.value, f.value, repr(s.value))
# brref 传入数据类型返回指针的地址, create_string_buffer返回的是指针
c_lib.sscanf(b"1 3.14 Hello", b"%d %f %s", byref(i), byref(f), s)
print(i.value, f.value, repr(s.value))
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from ctypes import c_int, POINTER, cdll
i = c_int(42) # 创建一个int类型变量
pi = POINTER(c_int)(i) # 定义一个指向int类型的指针
print(pi.contents) # 打印指针指向的内存地址
print(pi.contents.value) # 打印指针指向的值
# 定义C库中的函数原型
sum_func = cdll.LoadLibrary('./test1.so').sums
sum_func.argtypes = [POINTER(c_int), POINTER(c_int)] # 定义函数参数类型为指针类型
sum_func.restype = POINTER(c_int) # 定义函数返回值类型为指针类型
pointer = POINTER(c_int)
# 调用sum()函数
a = c_int(1)
b = c_int(2)
c = sum_func(pointer(a), pointer(b)) # 将a、b的地址传递给sum()函数
print(c.contents.value) # 打印返回值
|
5 操作结构体和联合体
你需要定义结构体或联合体的类型,然后可以创建实例、访问其成员等。
1
2
3
4
5
6
7
8
9
|
# 定义C语言的结构体类型
class Point(ctypes.Structure):
_fields_ = [("x", ctypes.c_int), ("y", ctypes.c_int)]
# 创建结构体的实例
p = Point()
p.x = 10
p.y = 20
# 将结构体的实例传递给C函数
lib.print_point(p) # 假设有一个print_point函数用于打印点的坐标
|
6 处理字符串
字符串通常以字符数组或字符指针的形式存在。在ctypes中,你可以使用create_string_buffer来创建C风格的字符串,或者使用c_char_p来操作字符串指针。
1
2
3
4
5
6
7
8
|
# 创建C风格的字符串
c_str = ctypes.create_string_buffer(b"Hello, World!")
# 将字符串传递给C函数
lib.print_string(c_str) # 假设有一个print_string函数用于打印字符串
# 处理C语言中的字符串指针
c_char_p_type = ctypes.POINTER(ctypes.c_char)
c_char_p = c_char_p_type.from_buffer(c_str)
lib.print_string_ptr(c_char_p) # 假设有一个print_string_ptr函数接收字符串指针
|
7 回调函数
c 中很多实现异步的方式通过回调函数事件触发的方式,ctype中也能将ctype定义的函数传入c中执行
1
2
3
4
5
6
7
8
9
10
|
# include "stdio.h"
typedef int (*CallbackFunc)(int, int);
int c_sub(int x, int y){
printf("c callback func\n");
return x - y;
}
void call_callback(CallbackFunc callback){
int result = callback(3, 4);
printf("result from c: %d\n", result);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
import ctypes
my_lib = ctypes.CDLL("./test2.so")
callback_type = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int)
def python_sub(a, b):
print("python callback func")
return b - a
# 1.调用c中的函数,回调函数从python中传入
python_callback = callback_type(python_sub)
my_lib.call_callback(python_callback)
# 2.调用c中的函数,回调从c中传入
c_callback = callback_type(my_lib.c_sub)
my_lib.call_callback(c_callback)
|
|