Python ctypes调用动态库的完整过程
Python ctypes调用动态库的完整过程ctypes允许Python直接调用C动态库中的函数。加载、参数传递、返回值和错误处理的完整流程如下。加载动态库from ctypes import *# Linuxlibc CDLL(libc.so.6)# macOS# libc CDLL(libc.dylib)# Windows# libc CDLL(msvcrt.dll)print(libc.time(None)) # 调用C标准库的time函数CDLL根据文件路径加载动态库。加载后的对象能直接调用库中的函数。函数签名的声明libc.printf.restype c_intlibc.printf.argtypes [c_char_p]libc.printf(bHello %s\n, bWorld)restype设置返回值类型。argtypes设置参数类型列表。正确设置后ctypes自动进行类型转换。自定义返回类型class Point(Structure):_fields_ [(x, c_int), (y, c_int)]lib CDLL(./mylib.so)lib.create_point.restype Pointlib.create_point.argtypes [c_int, c_int]p lib.create_point(10, 20)print(p.x, p.y) # 10 20Structure的子类映射C结构体。_fields_定义结构体字段。指针和引用操作from ctypes import *value c_int(42)ptr pointer(value)print(ptr.contents) # c_int(42)null_ptr POINTER(c_int)()print(bool(null_ptr)) # Falsepointer创建指向对象的指针。POINTER创建类型化指针。contents解引用指针。byref与pointer的区别from ctypes import *value c_int(0)# byref创建轻量级引用只用于函数参数传递libc.time(byref(value))# pointer创建完整的指针对象更重但更灵活ptr pointer(value)libc.time(ptr)byref创建轻量级引用只能作为函数参数传递。pointer创建完整的指针对象。回调函数# 定义CALLBACK类型CMPFUNC CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))def py_cmp(a, b):return a[0] - b[0]cmp_func CMPFUNC(py_cmp)# 调用C库的qsortIntArray5 c_int * 5arr IntArray5(5, 1, 4, 2, 3)libc.qsort(arr, len(arr), sizeof(c_int), cmp_func)print(list(arr)) # [1, 2, 3, 4, 5]CFUNCTYPE创建C可调用的函数指针。第一个参数是返回值类型后续是参数类型。数组操作from ctypes import *# 创建固定大小数组TenInts c_int * 10arr TenInts()for i in range(10):arr[i] i * 2print(arr[3]) # 6# 通过指针传递数组libc.qsort(arr, 10, sizeof(c_int), cmp_func)c_int * 10创建长度为10的c_int数组类型。数组自动支持索引和迭代。字符串与字节转换from ctypes import *# c_char_p自动从bytes转换libc.printf(bString: %s\n, bhello)# 创建可变字节缓冲区buf create_string_buffer(256)libc.strcpy(buf, bfixed string)print(buf.value) # bfixed string# 宽字符字符串ws create_unicode_buffer(256)# wprintf需要对应设置create_string_buffer创建可变的C字符数组。buf.value返回Python bytes对象。结构体嵌套与位域from ctypes import *class Point(Structure):_fields_ [(x, c_int), (y, c_int)]class Rect(Structure):_fields_ [(upper_left, Point),(lower_right, Point),]r Rect(Point(0, 0), Point(10, 10))print(r.upper_left.x) # 0class Flags(Structure):_fields_ [(read, c_uint, 1),(write, c_uint, 1),(exec, c_uint, 1),(reserved, c_uint, 29),]f Flags(1, 0, 1, 0)print(f.read, f.write, f.exec) # 1 0 1位域语法(类型, 名称, 位数)。多个位域可能打包到同一个整数中。联合体from ctypes import *class Value(Union):_fields_ [(i, c_int),(f, c_float),(b, c_byte * 4),]v Value()v.i 42print(v.b) # 解释为4个字节Union映射C联合体。所有字段共享同一块内存。错误处理与异常import ctypesfrom ctypes import get_errno, set_errnolibc ctypes.CDLL(libc.so.6)libc.open.restype c_intlibc.open.argtypes [c_char_p, c_int]fd libc.open(b/nonexistent/file, 0)if fd -1:errno get_errno()raise OSError(errno, open failed)get_errno获取C errno值。ctypes在调用C函数后保持errno不变。内存管理from ctypes import *# 分配内存libc.malloc.restype c_void_plibc.malloc.argtypes [c_size_t]ptr libc.malloc(1024)if ptr:# 读写内存memset libc.memsetmemset.restype c_void_pmemset.argtypes [c_void_p, c_int, c_size_t]memset(ptr, 0, 1024)# 释放内存libc.free(ptr)C动态分配的内存必须用C的free释放。ctypes不管理C内存的生命周期。dlopen/加载时的路径解析# RTLD_GLOBAL将库符号导出到全局符号表lib CDLL(./mylib.so, use_errnoTrue, use_last_errorTrue)# use_errno跟踪C errno# use_last_error跟踪Windows GetLastError()RTLD_NOW立即解析所有符号RTLD_LAZY延迟解析。ctypes的性能开销from ctypes import *import timelibc CDLL(libc.so.6)libc.getpid.restype c_intdef c_call():return libc.getpid()def py_call():import osreturn os.getpid()t1 timeit.timeit(c_call, number100000)t2 timeit.timeit(py_call, number100000)print(fctypes: {t1:.4f}s, Python: {t2:.4f}s)ctypes函数调用每次约0.5-1μs开销类型检查、转换、C调用、结果转换。高频调用时不可忽略。ctypes二进制数据的直接操作from ctypes import *data (c_byte * 1024)()fill_bytes(data, b\x00, 1024)move(data, data[1:], 1023)memmove复制源到目标允许重叠。memmove复制但不允许重叠。fill_bytes用指定字节填充。