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.com指針類型
指針數組類型
結構體指針類型
結構體指針數組類型
函數指針
類型轉換
獲取函數返回值類型
二.處理字節流
在使用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中每個數據的長度對字節流先進行切片,然后再進行大小端轉換。
總結
以上是生活随笔為你收集整理的python能和c语音交互吗_Python和C语言交互--ctypes,struct的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux野指针追踪,【华清远见】野指针
- 下一篇: php 查找键名,array_key_e