我比较习惯使用c++,但是最近遇到需要读取数据后画图的问题,我是以对象的形式保存数据的,可以把数据存到文本文档中,然后再用python或matlab读取画图,但是这也太麻烦了,不想这么干(大量的数据),于是就想能不能把c++程序封装成模块,然后以python为主体去调用它们。最后发现了这个库:ctypes。它是python的外部函数库,里面包含了C/C++的很多数据类型,我们可以利用这个库来调用编写好的C/C++模块(通常这些模块要编译成动态链接库供python使用)。下面对使用ctypes的方法进行简单的介绍。
1. 对于C/C++程序的要求
C:没什么要求,正常写就行。
C++:要在源文件的函数定义之前,用extern "C" {}
对定义的函数进行声明,因为[1]python的ctypes可以调用C而无法调用C++代码,加了extern "C"
之后会让编译器按照C语言而不是C++的方式进行编译。C++代码可以使用类等数据结构或C++头文件,只不过函数在定义前必须要提前声明在extern "C"{}
里边。
|
|
2. 生成动态链接库
对于C++源文件,我使用g++编译器进行编译(对C源文件则使用gcc),编译动态链接库时必须给编译器指明(可能用到的)库文件、头文件目录。。
动态链接库的文件名最好以“lib”开头[1],原因是gcc/g++编译选项-l后面只能接动态链接库文件名“libXXX.so”除去“lib”和扩展名之后的部分,比如g++ test.cpp -lXXX
。但也不是必须的,若不以“lib”开头,则编译选项只能用-L指定库的路径,例如g++ test.cpp -L ./libXXX.so
。当然,和普通编译程序一样。
综上所述,若此动态链接库将来不会用于gcc/g++的-l选项编译过程,则此库的文件名没有限制。
例如,以下命令对test.cpp
源文件进行编译,输出动态链接库.so文件。
|
|
3. 对于python程序的要求
若要调用动态链接库libtest.so
内的函数,则首先应import库。然后使用cdll对象的加载库方法,加载动态链接库到libc对象,通过libc对象的方法便可以调用到动态链接库中的函数[2]。
|
|
将会输出结果
|
|
4. 使用ctypes时python与C/C++程序互相传递参数
上面是最简单的调用C++函数的例子,若调用过程有参数传递,则在python程序中应注意数据类型的规范定义。因为python默认函数的参数类型和返回类型为 int 型[3],所以与外部函数传参、返回值时,变量必须用ctypes数据类型来明确定义,常用的ctypes数据类型见官方文档。C/C++代码仍按照自己的方式正常传递参数。下面我来结合例子进行详细说明。
例子 C++源文件
在test.cpp中定义了两个函数,分别传递整形和整形指针参数。
|
|
用以上源文件生成动态链接库libtest.so
。
例子 python代码
下面是python代码及程序运行结果。
|
|
输出结果为
|
|
ctypes外部函数的参数及返回值类型声明
对于外部函数的输入参数
,则要用argtypes方法和参数类型的列表或元组(元组效率比列表高)来定义,例如
|
|
[3]对于外部函数的返回值
,用外部函数的restype方法进行数据类型的定义,如
|
|
ctypes数据类型的规范定义
对于将要传递给外部函数的变量,用ctypes数据类型对变量进行定义,形式类似于强制类型转换,例如定义C浮点型变量1.234:a = c_float(1.234)
,通过ctypes变量的value方法访问、赋值变量的值:a.value = 2.345
,print(a.value)
。
数组
对于数组,直接使用元素数据类型*n即可,例如,array = c_int * 4
。
字符串
对于字符串,C语言函数所需的参数一般是字符指针,所以需要把python中的字符串(对象)转换成C语言的字符串(字节串)再进行参数传递。有两种方法实现,既可以使用python内置的bytes()
对象[4],也可以使用python内置的字符串之encode()
方法[5]。
|
|
|
|
利用第一种方法,把不同字符串生成的字符指针,再用字符指针数组进行声明,就可以传递字符串数组。
|
|
|
|
结构体
对于结构体,必须定义一个继承自ctypes中Structrue
的类,类中必须含有_fields_属性
,它是一个二元组构成的列表,每个二元组为(field name, field type)
,C的结构体中所有成员变量都以此种方式进行声明。该子类还有可选的一个_pack_
属性,用于规定实例中结构体的字节对齐方式。注意,把结构体向外部函数传参时,应该总是通过指针传递。
|
|
指针
对于指针型变量,见下面的表格。
ctypes内的函数 | 说明 |
---|---|
byref(obj, offset) | 返回obj的地址(指向obj的指针),obj为一个ctypes数据类型对象,offset为地址偏移量。该方法生成的指针只能作为参数来调用外部函数。 |
pointer(obj) | 返回一个指向obj的指针实例。 |
POINTER(type) | 返回一个指向type类型的指针类型,type必须是ctypes数据类型。 |
后两个的联系为,pointer(obj) == POINTER(obj_type)(obj)
。如果只是想生成一个调用外部函数所需要的指针,则使用byref
函数最合适,它构造起来最快,但返回的指针无法用于其他用途。
|
|
运行结果为
|
|