在Python与C语言混合编程的场景中,ctypes模块提供了一种强大的机制,允许Python直接调用C语言编写的动态链接库(DLL或.so文件)。其中,定义与C语言结构体(struct)对应的Python类是关键步骤之一。
一、ctypes.Structure基础
1.1 导入ctypes模块
首先,需要在Python脚本中导入ctypes模块:
import ctypes
1.2 定义与C struct对应的Python类
要定义一个与C语言结构体对应的Python类,需要继承ctypes.Structure基类,并在类中定义_fields_属性。_fields_是一个由元组组成的列表,每个元组包含两个元素:字段名称(字符串)和字段类型(ctypes类型)。
示例1:简单结构体
假设有一个C语言结构体定义如下:
// example.h
typedefstruct {
int id;
char name[50];
float salary;
} Employee;
对应的Python类可以这样定义:
classEmployee(ctypes.Structure):
_fields_ = [
("id", ctypes.c_int),
("name", ctypes.c_char * 50),
("salary", ctypes.c_float)
]
1.3 创建结构体实例
定义好结构体类后,可以创建其实例并初始化字段值:
emp = Employee()
emp.id = 1001
emp.name = b"John Doe"# 注意:C的char数组需要字节字符串
emp.salary = 50000.0
二、结构体嵌套与复杂类型
2.1 嵌套结构体
C语言结构体可以嵌套其他结构体,Python中同样可以通过嵌套ctypes.Structure子类来实现。
示例2:嵌套结构体
C语言定义:
// example.h
typedefstruct {
int x;
int y;
} Point;
typedefstruct {
Point position;
char name[50];
} GameObject;
Python实现:
classPoint(ctypes.Structure):
_fields_ = [
("x", ctypes.c_int),
("y", ctypes.c_int)
]
classGameObject(ctypes.Structure):
_fields_ = [
("position", Point), # 嵌套Point结构体
("name", ctypes.c_char * 50)
]
# 创建实例
obj = GameObject()
obj.position.x = 10
obj.position.y = 20
obj.name = b"Enemy"
2.2 结构体数组
C语言中经常使用结构体数组,Python中可以通过类型乘法来定义结构体数组类型。
示例3:结构体数组
C语言定义:
// example.h
#define MAX_EMPLOYEES 10
typedefstruct {
int id;
char name[50];
} SimpleEmployee;
typedefstruct {
SimpleEmployee employees[MAX_EMPLOYEES];
int count;
} EmployeeList;
Python实现:
classSimpleEmployee(ctypes.Structure):
_fields_ = [
("id", ctypes.c_int),
("name", ctypes.c_char * 50)
]
# 定义结构体数组类型
SimpleEmployeeArray = SimpleEmployee * 10
classEmployeeList(ctypes.Structure):
_fields_ = [
("employees", SimpleEmployeeArray), # 结构体数组
("count", ctypes.c_int)
]
# 创建实例并初始化
emp_list = EmployeeList()
emp_list.count = 3
for i inrange(emp_list.count):
emp_list.employees[i].id = 1000 + i
emp_list.employees[i].name = f"Emp{i}".encode('utf-8')
三、结构体与C函数的交互
3.1 加载动态链接库
要使用C函数,首先需要加载编译好的动态链接库(.dll或.so文件):
# Windows
lib = ctypes.WinDLL('example.dll')
# Linux/macOS
lib = ctypes.CDLL('./example.so')
3.2 声明函数原型
在调用C函数前,需要声明其返回类型和参数类型,以确保类型安全:
# 假设C函数原型:void print_employee(Employee *emp);
lib.print_employee.argtypes = [ctypes.POINTER(Employee)]
lib.print_employee.restype = None
3.3 调用C函数并传递结构体
可以通过byref或pointer函数将结构体实例传递给C函数:
# 使用byref传递结构体指针
lib.print_employee(ctypes.byref(emp))
# 或者使用pointer(返回指针对象,可访问contents属性)
emp_ptr = ctypes.pointer(emp)
lib.print_employee(emp_ptr)
3.4 完整示例
C代码(example.c)
#include<stdio.h>
#include<string.h>
typedefstruct {
int id;
char name[50];
float salary;
} Employee;
voidprint_employee(Employee *emp) {
printf("ID: %d\n", emp->id);
printf("Name: %s\n", emp->name);
printf("Salary: %.2f\n", emp->salary);
}
intmain() {
Employee emp = {1001, "John Doe", 50000.0};
print_employee(&emp);
return0;
}
编译动态链接库
- • Windows(使用MinGW或MSVC):
gcc -shared -o example.dll example.c
- • Linux/macOS:
gcc -shared -fPIC -o example.so example.c
Python代码(test.py)
import ctypes
# 定义Employee结构体
classEmployee(ctypes.Structure):
_fields_ = [
("id", ctypes.c_int),
("name", ctypes.c_char * 50),
("salary", ctypes.c_float)
]
# 加载动态链接库
lib = ctypes.CDLL('./example.so') # Linux/macOS
# lib = ctypes.WinDLL('example.dll') # Windows
# 声明函数原型
lib.print_employee.argtypes = [ctypes.POINTER(Employee)]
lib.print_employee.restype = None
# 创建结构体实例
emp = Employee()
emp.id = 1001
emp.name = b"John Doe"
emp.salary = 50000.0
# 调用C函数
lib.print_employee(ctypes.byref(emp))
四、高级主题
4.1 结构体对齐与_pack_属性
C语言结构体的内存对齐可能因编译器和平台而异。为了确保Python与C之间的结构体布局完全一致,可以使用_pack_属性指定对齐方式:
classPackedEmployee(ctypes.Structure):
_pack_ = 1# 1字节对齐
_fields_ = [
("id", ctypes.c_int),
("name", ctypes.c_char * 50),
("salary", ctypes.c_float)
]
4.2 结构体与字节数据的转换
有时需要将结构体实例转换为字节数据(如网络传输或文件存储),或从字节数据恢复结构体实例:
结构体转字节
defstruct_to_bytes(struct_instance):
size = ctypes.sizeof(struct_instance)
buffer = (ctypes.c_byte * size)()
ctypes.memmove(buffer, ctypes.addressof(struct_instance), size)
returnbytes(buffer)
# 示例
emp_bytes = struct_to_bytes(emp)
字节转结构体
defbytes_to_struct(byte_data, struct_type):
buffer = (ctypes.c_byte * len(byte_data)).from_buffer_copy(byte_data)
struct_instance = struct_type()
ctypes.memmove(ctypes.addressof(struct_instance), buffer, len(byte_data))
return struct_instance
# 示例
restored_emp = bytes_to_struct(emp_bytes, Employee)
4.3 动态结构体字段
在某些场景下,结构体的字段可能在运行时确定。虽然_fields_必须在类定义时指定,但可以通过继承和动态创建类来实现类似功能:
defcreate_dynamic_struct(field_definitions):
classDynamicStruct(ctypes.Structure):
pass
DynamicStruct._fields_ = field_definitions
return DynamicStruct
# 示例
fields = [("a", ctypes.c_int), ("b", ctypes.c_double)]
DynamicStruct = create_dynamic_struct(fields)
ds = DynamicStruct()
ds.a = 10
ds.b = 3.14
通过继承ctypes.Structure并定义_fields_属性,可以轻松创建与C语言结构体对应的Python类。这不仅简化了Python与C之间的数据交换,还为高性能计算、系统编程和硬件交互等场景提供了强大的支持。掌握结构体的定义、嵌套、数组以及与C函数的交互,是Python混合编程中的关键技能。结合_pack_属性、字节数据转换和动态结构体字段等高级技巧,可以进一步增强代码的灵活性和可移植性。