Python和C语言交互--ctypes,struct

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

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

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

1.ctypes的使用
C语言代码如下

#include <stdio.h>

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(数据类型详细踩坑指南)27 赞同 · 2 评论文章

  • 指针类型
  • 指针数组类型
  • 结构体指针类型
  • 结构体指针数组类型
  • 函数指针
  • 类型转换
  • 获取函数返回值类型

二.处理字节流

在使用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中每个数据的长度对字节流先进行切片,然后再进行大小端转换。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值