通过ctypes向C程序传递一维和二维数组(Linux环境下)
使用ctypes可以在python中调用C程序,它提供与C相兼容的数据类型,比如整数类型,浮点数类型,数组等等。这篇文章主要在Linux环境下进行,作为自己日常学习的笔记,如有不对的地方欢迎拍砖。
我们先从最简单的一维数组的例子开始,比如我们有个C函数addOne它的作用是使输入的数组的每个元素+1, 我们想用python程序调用它,从python中向该c函数输入参数,在c程序中将每个值+1之后将数组返还给python程序,该联合的程序主要有两个部分构成:C语言部分和python部分,依次进行说明。
C程序部分的编写和生成链接库
1 //addOne.c 2 //这个程序将输入数组的每个元素值+1 3 //输入参数:数组a,及a的元素个数n 4 void addOne(unsigned char *a,int n) 5 { 6 for(int i=0;i<n;i++) 7 { 8 a[i]++; 9 } 10 }
我们写好了这个程序之后,我们需要让他成为动态链接库 addOne.so
$ gcc addOne.c -fPIC -shared -o libAddOne.so
其中几个参数的含义是:
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的(参考自http://blog.sina.com.cn/s/blog_54f82cc201011op1.html)
-shared 该选项指定生成动态链接库
-o 指定输出目标名称
在python脚本中调用该库
1 #addOneMain.py
2 import ctypes 3 arr=(ctypes.c_uint8*3)(0,1,2) 4 5 adder=ctypes.CDLL('./libAddOne.so') 6 adder.addOne(arr,3) 7 8 for i in range(0,len(arr)): 9 print(arr[i],end=' ') 10 print()
该程序首先定义了一个与C相兼容的数组arr,该数组的创建方法是将一个ctypes的基本数据类型乘以一个正整数。之后使用CDLL实例化了一个对象adder,该adder对象中有addOne这个方法。之后将arr这个数组传递给C函数,再将处理结果打印出来。运行上面的python程序可以得到结果为:
1 2 3
由上面的小例子可以引出一下的一些基本知识。
ctypes 常用数据类型
我平时在写程序的时候,经常用到的几个ctypes数据类型分别有:基本数据类型,数组,指针。
基本数据类型
ctypes 中的常用基本数据类型如下表,完整的表格可以参考https://docs.python.org/3.6/library/ctypes.html
ctypes 类型 | C 类型 | python 类型 |
c_int8 | char | int |
c_uint8 | unsigned char | int |
c_float | float | float |
c_bool | _Bool (C99 标准) | bool(1) |
数组
ctypes 官方所推荐的构成一维数组的方法是将ctypes中的基本类型乘以一个正整数,比如上述例子中构造一个c_uint8型数组arr,其中包含3个元素,分别是"0,1,2",我们就可以这样构造它
arr=(ctypes.c_uint8*3)(0,1,2)
当然,我们也可以不明确初始化其值,ctypes会默认将所有元素的值设置为0
arr=(ctypes.c_uint8*3)()
这样数组中每个元素的值都是0。在学会使用ctypes声明一维数组之后,我们来讨论一下如何声明二维数组,上个例子中的一维数组,每个元素是一个uint8类型的值,那么二维数组是什么呢,二维数组相当于一个数组的外面又套着一个数组,我们可以通过下面的代码来体会一下。
brr=((ctypes.c_int*2)*3)((ctypes.c_int*2)(1,2),(ctypes.c_int*2)(3,4),(ctypes.c_int*2)(5,6))
通过这样的声明,我们就能得到一个3*2的数组,该数组相当于有一个1*3的数组brr,它的每个元素都是一个1*2的uint8型的数组。当然,我们也可以不先对它赋值,这样ctypes就会自动为每个值赋值为0,即
brr=((ctypes.c_int*2)*3)()
接下来,我们通过一个例子来验证一下,我们在python中声明这个数组brr,然后将它传递给C程序,
1 #initArrayMain.py 2 import ctypes 3 4 brr=((ctypes.c_int*2)*3)() 5 init=ctypes.CDLL('./libInitArray.so') 6 init.initArr(brr) 7 for i in range(0,6): 8 print(brr[int(i/2)][i%2],end=' ') 9 if i%2: 10 print()
在C程序中将0-5依次赋值给这6个元素
1 //initArray.c 2 void initArr(int arr[][2]){ 3 for(int i=0;i<6;i++) 4 { 5 arr[i/2][i%2]=i; 6 } 7 }
最后运行结果:
0 1 2 3 4 5
指针
将一个ctypes的基本类型的变量通过ctypes.pointer()函数就可以声明一个指向该变量的指针啦
i=ctypes.c_int(8)
pi=ctypes.pointer(i)