Python语言与C语言数据交互的场景还是比较常见的,例如在使用python优秀的数据处理可视化等优势的同时,对于某些优秀的开源C/C++的软件库的调用就需要用到ctypes库函数对动态库进行API的灵活调用了,再例如在某些场景下,C语言的数据需要可视化,而C语言的可视化接口的支持是很薄弱的,这里可以采用Python强大的可视化效果来验证数据的正确性(也可以采用MATLAB完成可视化)。 再比如在某些Demo测试场景下,使用Python便捷部署的神经网络框架在使用获取内存图像数据时也可能使用到Python和C之间数据交换的需求。
一、python与C交互的重要库 ctypes
ctypes库作为python与C之间的交互的重要库,其定义了各类数据类型与C语言中的数据类型进行对应,其中包括了char,int,POINTER等等,具体可以参看数据手册。ctypes还能够通过CDLL接口应用C语言的动态库.so,在调用接口过程中,应该要严格配置Python端调用动态库函数接口的参数类型(不能有任何偏差),应该掌握如何定义数组缓冲区并操作地址变量,同时能够使用接口获取变量地址,从而传给C-API使用,基础如下所示:
1. 数组类型定义及使用
测试代码:
1 # 特殊ctypes类型数组类型定义
2 ArrayTestType = ctypes.c_uint8 * 10 # 重定义数据类型
3 ArrayTest = ArrayTestType(11) # 定义数据并初始化数据赋值为11
4 ArrayTest[5] = 20 # 对数据进行赋值处理
5 print('Line', sys._getframe().f_lineno, ':', ArrayTest, type(ArrayTest), ArrayTestType, type(ArrayTestType))
6 print('Line', sys._getframe().f_lineno, ':', ArrayTest[0], ArrayTest[5])
运行结果:
Line 20 : <__main__.c_ubyte_Array_10 object at 0x7fd1318e8ea0> <class '__main__.c_ubyte_Array_10'> <class '__main__.c_ubyte_Array_10'> <class '_ctypes.PyCArrayType'> Line 21 : 11 20
这里可以看到 ArrayTestType 的类型其实为 PyCArrayType PythonC数组类型,因此 ArrayTestType 可以定义一个 unsigned char Array[10] 的数组。
注:数组类型的缩略定义方法(相当于上述步骤的两步)
# 定义一个大小为10指针实例作为缓存, 等效为 Step1:DataType = c_uint8 * LENGTH --> Step2:DataPoint = DataType() DataPoint = (c_uint8 * LENGTH)()
2. 动态库的加载及接口调用
测试代码:
1 # 从C语言或者C++的动态库当中加载C函数以调用 2 p = os.getcwd() + '/libfunc.so' # 获取当前的动态库的绝对路径位置 3 f = cdll.LoadLibrary(p) # 使用LoadLibrary接口加载C语言动态库 4 5 # Python Class类 到 C struct结构体 数据类型的传输转换 6 Sfunction = f.py_struct_address # 从当前库当中取得clib中的函数py_struct_address,并重命名为Sfunction 7 Sfunction.argtypes = [POINTER(POINT), POINTER(ctypes.c_char)] # 设置当前函数的输入参数
这里使用了 os模块获取了动态库的绝对路径,并调用cdll.LoadLibrary(libpath),这里使用 argtypes 定义了动态库函数的输入参数类型,分别为结构体指针、char *指针。
3. 定义字符串类型(字符串string)
1 p = create_string_buffer(b"Hello World", 15) # create a 10 byte buffer
2 print('Line', sys._getframe().f_lineno, ':', p,sizeof(p), repr(p.raw))
创建一个string类型的缓冲空间,并返回一个字符串指针指向这串字符串,create_string_buffer 参数分别为字符串、buffer总长度,这个长度不能小于前一个参数字符串的长度大小,否则会报错,p.raw取字符串的内容。
4. Numpy数组的地址获取及应用
测试代码:
1 # Numpy 数据类型等相互转换测试(将内存数据转换值Python当中)
2 ImgW = 1669 # 图像宽度
3 ImgH = 21 # 图像高度
4 ImgC = 3 # 图像通道数
5 ImgL = ImgW*ImgH*ImgC # 图像总长度
6
7 ImgArray = np.zeros((ImgW,ImgH,ImgC), dtype=np.ubyte) # 申请图像总空间为多维 zeros 矩阵
8 print(ImgArray.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8))) # 将numpy数组转换为地址表示方式 data_as 并打印数据类型
9 ImgArray_addr = ImgArray.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)) # 获取的指针放置在ImgArray_addr变量当中
10 print('Line', sys._getframe().f_lineno, ':', ImgArray_addr[0],ImgArray_addr[1],ImgArray_addr[2],ImgArray_addr[3])
11
12 Imgfunction = f.py_img_address # 从当前库当中取得clib中的函数py_struct_address,并重命名为Sfunction
13 Imgfunction.argtypes = [POINTER(ctypes.c_ubyte), ctypes.c_uint32] # 设置当前函数的输入参数规划 usigned char *pointer, unsigned int arg
14 Imgfunction.restype = ctypes.c_int # 设置当前函数返回值参数为 int 类型
15 time_start = time.time()
16 Res = Imgfunction(ImgArray_addr, ImgL) # 调用Imgfunction函数
17 time_end = time.time()
18 print('Line', sys._getframe().f_lineno, ':', 'TimeCost:', (time_end - time_start)*1000, 'ms') # 记录当前函数调用消耗的时间
19
20 print('Line', sys._getframe().f_lineno, ':/n', ImgArray[:,:,0]) # 打印0通道的数据
21 print('Line', sys._getframe().f_lineno, ':/n', ImgArray[:,:,1]) # 打印1通道的数据
22 print('Line', sys._getframe().f_lineno, ':/n', ImgArray[:,:,2]) # 答应2通道的数据
通过numpy库中数组的成员 ctypes.data_as 方法获取指定类型的指针地址,并将此地址应用在函数库的参数中。
5. 基本类型定义
测试代码:
1 # 使用byref获取ctypes类型数据的地址
2 data = ctypes.c_uint8(10) # 定义一个整数类型的变量,变量初始值为 10,相当于C语言中的 char data=42;
3 data_addr = ctypes.byref(data, 0) # 通过使用byref接口获取地址,相当于C语言中的 char *data_addr = &data; byref(obj, offset) 对应于这段 C 代码:(((char *)&obj) + offset)
4 print('Line', sys._getframe().f_lineno, ':', type(data))
5 print('Line', sys._getframe().f_lineno, ':', type(data_addr))
这里使用 ctypes.c_uint8(10) 定义一个 unsigned char 类型的数据并赋予初值为10,byref函数接口用于获取数据data的地址。
I) 完整的测试代码:
MemoryTest.py

1 import os
2 import sys
3 import time
4 import ctypes
5 from ctypes import *
6 from ctypes import cdll
7 import numpy as np
8
9 BUFF_SIZE = 6*1024*1024 # 基本的缓冲区域尺寸大小定义
10 LENGTH = 2*2*3 # 数据长度定义
11
12 # 类定义
13 class POINT(Structure): # 定义了一个类,类当中的基本成员变量包括了x、y, 相当于C语言中的 struct POINT{int x;inty};
14 _fields_ = [("x", c_int),("y", c_int),("addr", POINTER(c_uint8)),("len", c_int)]
15
16 # 特殊ctypes类型数组类型定义
17 ArrayTestType = ctypes.c_uint8 * 10 # 重定义数据类型
18 ArrayTest = ArrayTestType(11) # 定义数据并初始化数据赋值为11
19 ArrayTest[5] = 20 # 对数据进行赋值处理
20 print('Line', sys._getframe().f_lineno, ':', ArrayTest, type(ArrayTest), ArrayTestType, type(ArrayTestType))
21 print('Line', sys._getframe().f_lineno, ':', ArrayTest[0], ArrayTest[5])
22
23 DataPoint = (c_uint8 * LENGTH)() # 定义一个大小为10指针实例作为缓存, 等效为 Step1:DataType = c_uint8 * LENGTH --> Step2:DataPoint = DataType()
24 DataPoint[3] = 22
25
26 point = POINT(10, 20, DataPoint,LENGTH) # 定义个类对象Obj, 相当于 struct POINT point={10,20};
27 print('Line', sys._getframe().f_lineno, ':', point.x, point.y, point.addr[3], point.len)
28
29 point = POINT(y=5) # 定义个类对象Obj, 相当于 struct POINT point={,20};
30 print('Line', sys._getframe().f_lineno, ':', point.x, point.y)
31
32 # Class类数组定义
33 addrTest = (POINT * 1)() # 定义一个POINT结构体缓冲地址
34 addrTest[0].x = 10 # POINT缓冲地址的第一个变量的x成员值
35 addrTest[0].y = 11 # POINT缓冲地址的第一个变量的y成员值
36 addrTest[0].addr = DataPoint # POINT缓冲地址的第一个变量的addr成员赋值
37 addrTest[0].len = LENGTH # POINT缓冲地址的第一个变量的len成员赋值
38 print('Line', sys._getframe().f_lineno, ':', addrTest, addrTest[0].x,addrTest[0].y,addrTest[0].addr[3],addrTest[0].len)
39
40 TenPointsArrayType = POINT * 3 # 重定义了一个POINT数组类型,相当于C语言中的 #define TenPointsArrayType POINT*3
41 arr = TenPointsArrayType()
42 for pt in arr:
43 print(pt.x, pt.y, pt.addr)
44
45 # 从C语言或者C++的动态库当中加载C函数以调用
46 p = os.getcwd() + '/libfunc.so' # 获取当前的动态库的绝对路径位置
47 f = cdll.LoadLibrary(p) # 使用LoadLibrary接口加载C语言动态库
48 function = f.py_point_address # 从当前库当中取得clib中的函数py_point_address,并重命名为function
49 function.argtypes = [POINTER(c_byte)] # 设置当前函数的输入参数
50
51 # Python Class类 到 C struct结构体 数据类型的传输转换
52 Sfunction = f.py_struct_address # 从当前库当中取得clib中的函数py_struct_address,并重命名为Sfunction
53 Sfunction.argtypes = [POINTER(POINT), POINTER(ctypes.c_char)] # 设置当前函数的输入参数
54
55 p = create_string_buffer(b"Hello World", 15) # create a 10 byte buffer
56 print('Line', sys._getframe().f_lineno, ':', p,sizeof(p), repr(p.raw))
57
58 time_start = time.time()
59 Res = Sfunction(addrTest, p)
60 time_end = time.time()
61 for i in range(LENGTH):
62 print(addrTest[0].addr[i])
63
64 print('Line', sys._getframe().f_lineno, ':', addrTest[0].x, addrTest[0].y, addrTest[0].len, addrTest[0].addr)
65 print('Line', sys._getframe().f_lineno, ':', 'TimeCost:', (time_end - time_start)*1000, 'ms') # 记录当前函数调用消耗的时间
66
67 # Numpy 数据类型等相互转换测试(将内存数据转换值Python当中)
68 ImgW = 1669 # 图像宽度
69 ImgH = 21 # 图像高度
70 ImgC = 3 # 图像通道数
71 ImgL = ImgW*ImgH*ImgC # 图像总长度
72 # ImgArray = np.array([[0, 1], [2, 3]], dtype=np.uint8)
73 ImgArray = np.zeros((ImgW,ImgH,ImgC), dtype=np.ubyte) # 申请图像总空间为多维 zeros 矩阵
74 # ImgArray = np.array([1,2,3,4], dtype=np.int32)
75 # print(ImgArray)
76 # print(ImgArray.ctypes.data)
77 print(ImgArray.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8))) # 将numpy数组转换为地址表示方式 data_as 并打印数据类型
78 ImgArray_addr = ImgArray.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)) # 获取的指针放置在ImgArray_addr变量当中
79 print('Line', sys._getframe().f_lineno, ':', ImgArray_addr[0],ImgArray_addr[1],ImgArray_addr[2],ImgArray_addr[3])
80 # print(ImgArray.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)).contents)
81 # print(ImgArray.ctypes.data_as(ctypes.POINTER(ctypes.c_uint16)).contents)
82 # print(ImgArray.ctypes.shape)
83 # print(ImgArray.ctypes.strides)
84
85 Imgfunction = f.py_img_address # 从当前库当中取得clib中的函数py_struct_address,并重命名为Sfunction
86 Imgfunction.argtypes = [POINTER(ctypes.c_ubyte), ctypes.c_uint32] # 设置当前函数的输入参数规划 usigned char *pointer, unsigned int arg
87 Imgfunction.restype = ctypes.c_int # 设置当前函数返回值参数为 int 类型
88 time_start = time.time()
89 Res = Imgfunction(ImgArray_addr, ImgL) # 调用Imgfunction函数
90 time_end = time.time()
91 print('Line', sys._getframe().f_lineno, ':', 'TimeCost:', (time_end - time_start)*1000, 'ms') # 记录当前函数调用消耗的时间
92
93 print('Line', sys._getframe().f_lineno, ':/n', ImgArray[:,:,0]) # 打印0通道的数据
94 print('Line', sys._getframe().f_lineno, ':/n', ImgArray[:,:,1]) # 打印1通道的数据
95 print('Line', sys._getframe().f_lineno, ':/n', ImgArray[:,:,2]) # 答应2通道的数据
96
97 # 一般类型定义以及数据取地址方法 int *pi = &i;
98 i = ctypes.c_int(42) # 定义一个整数类型的变量,变量初始值为 42,相当于C语言中的 int i=42;
99 pi = ctypes.pointer(i) # 通过使用pointer接口获取,相当于C语言中的 int *pi = &i;
100 print('Line', sys._getframe().f_lineno, ':', pi.contents) # 查看指针变量的信息
101 print('Line', sys._getframe().f_lineno, ':', pi[0]) # 查看指针所指向的内容,相当于C语言中的 *pi;
102
103 # ctypes.c_byte 类型的数组定义,等效于 byte a[BUFF_SIZE];
104 a = (c_byte * BUFF_SIZE)() # 定义一个大小为BUFF_SIZE指针实例作为缓存
105 # cast(a, POINTER(c_uint8)) # 函数可以将一个指针实例强制转换为另一种 ctypes 类型
106 print('Line', sys._getframe().f_lineno, ':', a)
107 print('Line', sys._getframe().f_lineno, ':', type(a))
108 time_start = time.time()
109 function(a) # 执行function函数并传入a地址参数
110 time_end = time.time()
111
112 for i in range(10):
113 print(a[i])
114
115 print('Line', sys._getframe().f_lineno, ':', 'TimeCost:', (time_end - time_start)*1000, 'ms') # 记录当前函数调用消耗的时间
116
117 # 使用byref获取ctypes类型数据的地址
118 data = ctypes.c_uint8(10) # 定义一个整数类型的变量,变量初始值为 10,相当于C语言中的 char data=42;
119 data_addr = ctypes.byref(data, 0) # 通过使用byref接口获取地址,相当于C语言中的 char *data_addr = &data; byref(obj, offset) 对应于这段 C 代码:(((char *)&obj) + offset)
120 print('Line', sys._getframe().f_lineno, ':', type(data))
121 print('Line', sys._getframe().f_lineno, ':', type(data_addr))
122
123 # 使用id获取变量在python的地址
124 value = 'hello world' # 定义一个字符串变量
125 address = id(value) # 获取value的地址,赋给address
126 get_value = ctypes.cast(address, ctypes.py_object).value # 读取地址中的变量
127 print('Line', sys._getframe().f_lineno, ':', address, get_value) #
128
129 # 一般Clib函数的调用
130 res = f.func(99) # 普通函数调用
131 print('Line', sys._getframe().f_lineno, ':', res)
View Code
function.c

1 #include <stdio.h>
2 #include <sys/shm.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <time.h>
6 #include <sys/time.h>
7
8
9 #define BUFF_SIZE 6*1024*1024
10
11 typedef struct T_POINT{
12 int x;
13 int y;
14 char * addr;
15 int len;
16 }POINT;
17
18 time_t get_timestamp_us(void)
19 {
20 time_t timestamp_ms = 0;
21 struct timeval tv;
22
23 gettimeofday(&tv,NULL);
24 timestamp_ms = tv.tv_sec * 1000 * 1000 + tv.tv_usec;
25 return timestamp_ms;
26 }
27
28 char *file_read(unsigned long *file_bytes, char *file_name)
29 {
30 int file_size;
31 FILE *fd = NULL;
32 char *file_data = NULL;
33 fd = fopen(file_name, "rw");
34 if(fd < 0)
35 {
36 printf("File open failed.../n");
37 return NULL;
38 }
39 fseek(fd, 0, SEEK_END);
40 file_size = ftell (fd);
41 file_data = malloc(sizeof(char)*file_size);
42 if(file_data == NULL)
43 {
44 printf("Malloc failed.../n");
45 return NULL;
46 }
47 fseek(fd, 0, SEEK_SET);
48 *file_bytes = fread(file_data,sizeof(char),file_size,fd);
49 fclose(fd);
50 return file_data;
51 }
52
53 /* func.c */
54 int func(int a)
55 {
56 return a*a;
57 }
58
59 void cycle_calc(int b)
60 {
61 int count = 100;
62 while(count--){
63 b*=2;
64 printf("%d - %d/n", count, b);
65 }
66 }
67
68 unsigned char * c_point_address(void)
69 {
70 unsigned char *Img = malloc(sizeof(unsigned char)*1000);
71 printf("C-Address:%hhn/n", Img);
72 memset(Img, 20, 1000);
73 return Img;
74 }
75
76 int py_point_address(unsigned char * Addr)
77 {
78 unsigned char *Img = malloc(sizeof(unsigned char)*BUFF_SIZE);
79 // printf("C-Address:%x/n", Img);
80 // printf("Python-Address:%x/n", Addr);
81 memset(Img, 20, BUFF_SIZE);
82 memcpy((unsigned char * )Addr, Img, BUFF_SIZE);
83 return 1;
84 }
85
86 int py_struct_address(POINT *pt_POINT, char *str)
87 {
88 int i = 0;
89 for(i=0; i < pt_POINT->len; i++)
90 {
91 pt_POINT->addr[i] = i;
92 }
93 pt_POINT->x = 16;
94 pt_POINT->y = 17;
95 printf("FunctionPrint:%s/n",str);
96 return 1;
97 }
98
99 int py_img_address(unsigned char *data, unsigned int lenght)
100 {
101 time_t st,et;
102 unsigned long count=0;
103 unsigned long i=0;
104 st = get_timestamp_us();
105 unsigned char *ImgData = file_read(&i, "./Test.PNG");
106 et = get_timestamp_us();
107 printf("C ### ReadFile time Cost:%ld/n", et - st);
108
109 printf("Data[%ld]:%d/n", count, *(ImgData+i-1));
110
111 st = get_timestamp_us();
112 memcpy(data, ImgData, sizeof(unsigned char)*lenght);
113 et = get_timestamp_us();
114 printf("C ### Memcpy time Cost:%ld/n", et - st);
115
116 printf("py_img_address FunctionPrint:%ld/n", i);
117 return 1;
118 }
View Code
exe_shell.sh
1 #!/bin/bash 2 gcc -fPIC -shared function.c -o libfunc.so 3 python3 MemoryTest.py
II) 测试结果如下(测试平台: Ubuntu 18.04.6 LTS + Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz ):

Line 91 : TimeCost: 0.07772445678710938 ms 102KB Speed=1.25GB/s
二、采用共享内存方式进行IPC通信
内存共享基本方法参考 《进程间通信原理》 ,共享内存的方式需要通过其他通信方式进行进程间数据同步,从而保证共享内存在使用的过程中不被其他进程修改。
main.py

1 from ctypes import *
2 import numpy as np
3 import codecs
4 import datetime
5
6 SHM_SIZE = 1024*1024*20 # 20MBytes
7 SHM_KEY = 123559
8
9 OUTFILE="Shared.PNG"
10 try:
11 rt = CDLL('librt.so')
12 except:
13 rt = CDLL('librt.so.1')
14
15 shmget = rt.shmget
16 shmget.argtypes = [c_int, c_size_t, c_int]
17 shmget.restype = c_int
18 shmat = rt.shmat
19 shmat.argtypes = [c_int, POINTER(c_void_p), c_int]
20 shmat.restype = c_void_p
21
22 shmid = shmget(SHM_KEY, SHM_SIZE, 0o666)
23
24 if shmid < 0:
25 print ("System not infected")
26 else:
27 addr = shmat(shmid, None, 0)
28 f=open(OUTFILE, 'wb')
29 begin_time = datetime.datetime.now()
30 DataLength = int.from_bytes(string_at(addr,4), byteorder='little', signed=True) #这里数据文件是小端int16类型
31 ImgData = string_at(addr+4,DataLength)
32 end_time = datetime.datetime.now()
33 print(DataLength, ' Bytes')
34 print('Type:',type(ImgData),' Bytes:', len(ImgData))
35 f.write(ImgData)
36 f.close()
37 #print ("Dumped %d bytes in %s" % (SHM_SIZE, OUTFILE))
38 print("Success!",end_time-begin_time)
View Code
main.c

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/shm.h>
4 #include <string.h>
5 #include <time.h>
6 #include <sys/time.h>
7
8 #define SHAERD_MEM_SIZE 20 * 1024 * 1024 // 20MBytes
9
10 char mem_free(void *ptr);
11 time_t get_timestamp_ms(void);
12 char *file_read(unsigned int *file_bytes, char *file_name);
13
14 int main(int argc, char *argv[])
15 {
16 int id = 0;
17 size_t offset = 0;
18 char *data = NULL;
19 char *ImgData = NULL;
20 unsigned int file_bytes = 0;
21 time_t start_time, end_time;
22 if (argc < 2)
23 {
24 printf("args too less/n");
25 return 0;
26 }
27
28 id = shmget(123559, SHAERD_MEM_SIZE, IPC_CREAT | 0777);
29 if (id < 0)
30 {
31 printf("get id failed/n");
32 return 0;
33 }
34
35 data = shmat(id, NULL, 0);
36 if (data == NULL)
37 {
38 printf("shamt failed/n");
39 return 0;
40 }
41
42 ImgData = file_read(&file_bytes, argv[1]);
43 offset = sizeof(unsigned int);
44 printf("Size of unsigned long:%d/n", offset);
45 printf("Size of Image File:%d/n", file_bytes);
46 start_time = get_timestamp_ms();
47 memcpy(data, &file_bytes, sizeof(unsigned int));
48 memcpy(data + offset, ImgData, file_bytes);
49 end_time = get_timestamp_ms();
50
51 printf("Time Cost:%d/n", end_time - start_time);
52
53 mem_free(ImgData);
54
55 return 0;
56 }
57
58 char *file_read(unsigned int *file_bytes, char *file_name)
59 {
60 int file_size;
61 FILE *fd = NULL;
62 char *file_data = NULL;
63 fd = fopen(file_name, "rw");
64 if(fd < 0)
65 {
66 printf("File open failed.../n");
67 return NULL;
68 }
69 fseek(fd, 0, SEEK_END);
70 file_size = ftell (fd);
71 file_data = malloc(sizeof(char)*file_size);
72 if(file_data == NULL)
73 {
74 printf("Malloc failed.../n");
75 return NULL;
76 }
77 fseek(fd, 0, SEEK_SET);
78 *file_bytes = fread(file_data,sizeof(char),file_size,fd);
79 fclose(fd);
80 return file_data;
81 }
82
83 time_t get_timestamp_ms(void)
84 {
85 time_t timestamp_ms = 0;
86 struct timeval tv;
87
88 gettimeofday(&tv,NULL);
89 timestamp_ms = tv.tv_sec * 1000 + tv.tv_usec / 1000;
90 return timestamp_ms;
91 }
92
93 char mem_free(void *ptr)
94 {
95 if(NULL != ptr)
96 {
97 free(ptr);
98 return 0;
99 }
100 printf("Memory is Empty.../n");
101 return -1;
102 }
View Code
编译执行即可
gcc -o main main.c ./main sdlinux.zip # 12MB python3 main.py
测试结果如下(测试平台:Ubuntu 18.04.6 LTS + Intel(R) Core(TM) i5-10400F CPU @ 2.90GHz ):

Success! 7.091045379638672 ms 12MB Speed=1.67GB/s
未完待续 ~
原创文章,作者:端木书台,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/244399.html