python能和c语音交互吗_Python和C语言交互--ctypes,struct

python和c语言进行数据交互,涉及类型转换,字节对齐,字节序大小端转换等。相关模块ctypes,struct,memoryview。

一.ctypes:python和c语言使用结构体数据进行交互

场景:有一个C语言生成的动态链接库,python需要调用动态库里的函数处理数据。函数的入参是结构体类型的指针,出参是一个buffer,那么如何把python的数据转换成c语言中的结构体类型?

1.ctypes的使用

C语言代码如下

#include

typedef struct student{

char name;

short class;

double num;

int age;

}stu;

typedef struct stu_struct_array{

stu stu_array[2];

}stu_struct_array_t;

int struct_test(stu *msg, stu_struct_array_t *nest_msg, char *buff){

int index = 0;

printf("stu name: %d\n", msg->name);

printf("stu class: %d\n", msg->class);

printf("stu num: %f\n", msg->num);

printf("stu age: %d\n", msg->age);

memcpy(nest_msg->stu_array, msg, sizeof(stu));

printf("stu array index 0 name: %d\n", nest_msg->stu_array[0].name);

printf("stu array index 0 class: %d\n", nest_msg->stu_array[0].class);

memcpy(buff, msg, sizeof(stu));

printf("buff: %d %d", buff[0], buff[1]);

return 1;

}

通过-fPIC -shared选项生成动态链接库,编译命令gcc -Wall -g -fPIC -shared -o libstruct.so.0 struct_array.c

此时需要通过python调用struct_test()函数,那么如何利用python传入结构体参数呢?

方法就是利用ctypes模块组装结构体

(1)首先是结构体的组装

ctypes定义了一些和C兼容的基本数据类型:

_fields_需要包括(构体成员名称, C语言中的数据类型)组成的元组列表来初始化

from ctypes import *

# 根据结构体类型组装数据

fields_list = [("name", c_char),

("class", c_short),

("num", c_double),

("age", c_int)]

stu_value_list = [c_char(b'\x05'), c_short(1), c_double(10244096), c_int(2)]

# 创建结构体对象

class StuStruct(Structure):

# _fields_是容纳每个结构体成员类型和值的列表,可以配合自动生成fields list和value list的函数使用

_fields_ = fields_list

"""# 也可以直接初始化,适用于结构体数量不多的情况_fields_ = [("name", c_char, b'\x05),("class", c_short, 1),("num", c_double, 10244096),("age", c_int, 2)]"""

# 实例化并初始化结构体成员的值

stu_obj = StuStruct(*stu_value_list)

print("stu name:%s" % stu_obj.name)

# 这里使用的时候需要注意,结构体成员的名称不能和python内置关键字重复,如果真出现了这种情况。。。

# print(stu_obj.class)

print("stu num:%s" % stu_obj.num)

print("stu age:%s" % stu_obj.age)

# 创建嵌套结构体对象

class NestStu(Structure):

_fields_ = [("stu_array1", StuStruct * 2)

]

# 创建StuStruct的数组

stu_array = StuStruct * 2

stu_obj_list = [stu_obj, stu_obj]

# 实例化stu_array

stu_array_obj = stu_array(*stu_obj_list)

# 实例化NestStu,因为stu_array1成员是结构体数组类型,只能以同类型的实例进行初始化

nest_obj = NestStu(stu_array_obj)

# 打印信息

print("name:%s" % nest_obj.stu_array1[0].name)

print("num:%s" % nest_obj.stu_array1[0].num)

print("age:%s" % nest_obj.stu_array1[0].age)

# 载入动态链接库

struct_so = cdll.LoadLibrary("./libstruct.so.0")

# 调用函数,根据入参类型需要把结构体转换成对应的指针

stu_p = pointer(stu_obj)

nest_stu_p = pointer(nest_obj)

# ctypes模块提供了快速创建char类型数组的方法

in_buff =create_string_buffer(b"", size=100)

rest = struct_so.struct_test(stu_p, nest_stu_p, in_buff)

# 一般情况下若被调用的函数没有返回值,成功执行后则会返回0,若有其他返回值则返回对应的值

print("rest:%s" % rest)

这里使用的时候需要注意,结构体成员的名称不能和python内置关键字重复,如上述的class,如果真出现了这种情况。。。

(2)调用动态链接库,查看打印,获取输出stu name: b'\x05'

stu num: 10244096.0

stu age: 2

name: b'\x05'

num: 10244096.0

age: 2

stu name: 5

stu class: 1

stu num: 10244096.000000

stu age: 2

stu array index 0 name: 5

stu array index 0 class: 1

rest: 1

buff: 5 0

此处应该注意的一个问题是字节对齐的问题,ctypes模块提供了_pack_属性来设置字节对齐,默认不设置则跟编译器设置相同4字节对齐,如果设置为1字节对齐,需要更改代码,比如在StuStruct中加入_pack_ = 1,

# 创建结构体对象

class StuStruct(Structure):

# _fields_是容纳每个结构体成员类型和值的列表,可以配合自动生成fields list和value list的函数使用

_fields_ = fields_list

_pack_ = 1

print(sizeof(StuStruct))的输出为15,不指定字节对齐则为24。

此外,除了实现字节对齐以外,ctypes模块还提供了class BigEndingStructure()和class LittleEndingStructure(()用于创建大小端字节序的结构体,

更多方法请参照我的另一篇文章,里面详细介绍了使用Python组装C语言数据类型的方法。INnoVation:Python--ctypes(数据类型详细踩坑指南)​zhuanlan.zhihu.comzhihu-card-default.svg指针类型

指针数组类型

结构体指针类型

结构体指针数组类型

函数指针

类型转换

获取函数返回值类型

二.处理字节流

在使用python处理二进制数据或者使用socket通信的时候,python提供了struct模块将数据转换为字节流进行处理。

1.内置方法:def calcsize(fmt)

根据给定的fmt计算calsize大小

def pack(fmt, *args)

fmt:格式控制符,主要用于指定每一个需要解析的数据大小,格式控制符对应c语言的数据类型和size如下

*args:需要pack的值的列表

return:字节对象def pack_into(fmt, buffer, offset, *args)

将args列表内的数据pack为字节流。然后写入buffer内偏移量为offset以后的区域

def unpack(fmt, string)

将string根据fmt解析为一个元组然后返回

def unpack_from(fmt, buffer, offset=0)

从buffer区域offset位置开始截取字节流然后进行转换,返回一个元组

def iter_unpack(*fmt, **string)

先使用calsize计算fmt的大小,然后每次转换string中长度为每个fmt对饮大小的字节,返回的是每次unpack产生的值组成的一个unpack_iterator。

import struct

int_byte1 = b'\x01\x00\x00\x00\x02\x00\x03\x00\x00\x00'

fmt = "=IHI"

rest = struct.iter_unpack(fmt, int_byte1)

print(type(rest))

for item in rest:

print(item)

输出:(1, 2, 3)

2.字节序的转换

因为个人业务遇到了一种情况,本机为小端字节序,但是在转换为字节流的时候需要需要转换为大端字节序且需要满足4字节对齐的情况,这个时候struct模块提供的格式控制符就不能满足需求了,无论是'>'控制符还是'!'控制符均以本机字节序和1字节对齐为准进行转换。那么要实现上述的需求,只能先转换为本机字节序的字节流,再进行字节序的转换。

# 本机字节序,4字节对齐

print(struct.pack("@BH", 1, 2))

# 大端字节序,1字节对齐

print(struct.pack(">BH", 1, 2))

# 本机字节序,1字节对齐

print(struct.pack("=BH", 1, 2))

输出:b'\x01\x00\x02\x00'

b'\x01\x00\x02'

b'\x01\x02\x00'

实现方法:字节序的不同本质上是数据在内存内部的存放顺序不同,要完成字节序的转换只要改变数据再内存中的存放顺序即可,python提供了memoryview模块让我们能够直接操作内存,实现方法如下:

import struct

import sys

# 查看本机字节序

print(sys.byteorder)

# 使用本机字节序进行转换

bytes_stream = struct.pack("@I", 2)

print("little ending strm: %s" % bytes_stream)

# memoryview只接受bytearray对象,此处需要转换

array_stream = bytearray(bytes_stream)

mem_str = memoryview(array_stream)

stream_len = mem_str.__len__()

print("msg len: %s" % stream_len)

# 改变内存内值的排列顺序

for ite in range(0, stream_len):

tmp = mem_str[ite:ite + 1].tobytes()

mem_str[ite:ite + 1] = mem_str[stream_len - ite - 1:stream_len - ite]

mem_str[stream_len - ite - 1:stream_len - ite] = tmp

if ite + 1 == stream_len - 1 - ite:

break

mem_bytes = mem_str.tobytes()

print("big ending strm: %s" % mem_bytes)

输出:little

little ending strm: b'\x02\x00\x00\x00'

msg len: 4

big ending strm: b'\x00\x00\x00\x02'

此处演示只是对单个数据进行大小端处理,多数情况下一条字节流里可能含有多个数据,那样就需要根据fmt中每个数据的长度对字节流先进行切片,然后再进行大小端转换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值