张芷铭的个人博客

ctypes 是 Python 标准库的外部函数接口,允许直接调用 C 动态链接库中的函数。

核心价值

  • 调用系统 API:Windows Kernel32.dll、Linux libc.so
  • 复用 C 库:高性能数学计算、图像处理、硬件驱动
  • 跨语言交互:Rust、Fortran 等编译为 C 兼容库

加载动态库

加载器调用约定适用平台
CDLL / cdllcdeclLinux/Unix,Windows C 库
WinDLL / windllstdcallWindows API
OleDLL / oledllstdcall (HRESULT)Windows COM
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from ctypes import *

# Linux/macOS
libc = CDLL("libc.so.6")      # Linux
# libc = CDLL("libc.dylib")   # macOS

# Windows
# libc = windll.kernel32

# 自定义库
mylib = CDLL("./mylib.so")

函数原型定义

1
2
3
4
5
6
7
8
9
from ctypes import *

libc = CDLL("libc.so.6")

# 设置参数和返回类型(强烈建议)
libc.atoi.argtypes = [c_char_p]
libc.atoi.restype = c_int

result = libc.atoi(b"123")  # 123

数据类型

基础类型

ctypes 类型C 类型
c_intint
c_doubledouble
c_char_pchar*
c_void_pvoid*

结构体

1
2
3
4
5
6
7
8
9
class POINT(Structure):
    _fields_ = [("x", c_int), ("y", c_int)]

point = POINT(10, 20)
print(point.x, point.y)  # 10 20

# 结构体嵌套
class RECT(Structure):
    _fields_ = [("ul", POINT), ("lr", POINT)]

数组

1
2
3
4
# 定义数组类型
IntArray10 = c_int * 10
arr = IntArray10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
print(arr[0])  # 1

指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 创建指针
i = c_int(42)
pi = pointer(i)
print(pi.contents)  # c_long(42)

# 传递引用(更高效)
mylib.my_function(byref(i))

# 可写字符串缓冲区
buf = create_string_buffer(b"Hello", 10)
print(buf.value)  # b'Hello'

回调函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from ctypes import *

# 定义回调类型:返回值 + 参数类型
CMPFUNC = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))

@CMPFUNC
def py_cmp(a, b):
    return a[0] - b[0]

# 用于 qsort
IntArray5 = c_int * 5
ia = IntArray5(5, 1, 7, 33, 99)
libc.qsort(ia, len(ia), sizeof(c_int), py_cmp)
print(list(ia))  # [1, 5, 7, 33, 99]

实战示例

调用自定义 C 库

1
2
3
4
// mathlib.c
double hypotenuse(double a, double b) {
    return sqrt(a*a + b*b);
}
1
gcc -shared -fPIC -o libmathlib.so mathlib.c -lm
1
2
3
4
5
mathlib = CDLL('./libmathlib.so')
mathlib.hypotenuse.argtypes = [c_double, c_double]
mathlib.hypotenuse.restype = c_double

result = mathlib.hypotenuse(3.0, 4.0)  # 5.0

Windows API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from ctypes import *
from ctypes.wintypes import *

kernel32 = windll.kernel32
handle = kernel32.GetCurrentProcess()

# 获取模块路径
kernel32.GetModuleFileNameA.argtypes = [c_void_p, c_char_p, c_uint]
kernel32.GetModuleFileNameA.restype = c_uint

buf = create_string_buffer(1024)
kernel32.GetModuleFileNameA(None, buf, len(buf))
print(buf.value.decode())

最佳实践

  1. 类型安全:始终设置 argtypesrestype
  2. 调用约定:Windows 上区分 cdll(cdecl) 和 windll(stdcall)
  3. 内存管理:明确 C 函数内分配内存的所有权
  4. 错误处理:检查返回值,使用 faulthandler 调试崩溃
  5. 跨平台:用 ctypes.util.find_library 查找库

替代方案对比

技术优点缺点
ctypes标准库,无需编译性能开销大
CFFI更 Pythonic需单独安装
Cython性能极佳需要编译

Comments