ctypes与numpy.ctypeslib的使用
numpy ctypeslib 与 ctypes接口使用说明
作者:elfin
使用numpy.ctypelib或者ctypes库可以实现python直接待用C++。numpy.ctypeslib后端是基于ctypes实现的!所以接口是类似的。
如果只想看如何调用外部接口可以直接查看 1.4.3.1 动态链接库使用成功案例 ,其他部分有很多试错的过程!
一、numpy.ctypeslib使用说明
numpy是python做计算非常基础的一个库,安装就直接使用pip install numpy
即可。
导入python包:import numpy.ctypeslib as ctl
1.1 准备好一个C++计算文件
#include <iostream>
using namespace std;
int Paritition1(int A[], int low, int high) {
int pivot = A[low];
while (low < high) {
while (low < high && A[high] >= pivot) {
--high;
}
A[low] = A[high];
while (low < high && A[low] <= pivot) {
++low;
}
A[high] = A[low];
}
A[low] = pivot;
return low;
}
void QuickSort(int A[], int low, int high) //快排母函数
{
if (low < high) {
int pivot = Paritition1(A, low, high);
QuickSort(A, low, pivot - 1);
QuickSort(A, pivot + 1, high);
}
}
int main()
{
int arr[5] = { 9, 2, 8, -6, 5 };
QuickSort(arr, 0, 4);
for (int i = 0; i < 4; i++)
{
cout << arr[i] << " ";
}
cout << arr[4] << endl;
}
上面是一段C++版的快排代码。假设我们已经有这个cpp文件,那么如何在python中使用?
需要独立编译时,Windows系统可以在头文件中使用下面的语句
// __declspec(dllexport) 导出到库中(win系统环境下.dll文件链接宏) extern "C" __declspec(dllexport) void QuickSort(int A[], int low, int high);
在Linux中需要使用extern进行声明
1.2 ctypeslib主要的五个接口
ctypeslib给我们提供了五个接口使用:
load_library
加载c++文件;ndpointer
c_intp
as_ctypes
numpy数据类型转为ctypes类型as_array
c++数据转为numpy数据类型
1.3 加载编译后的文件
我这里是在Windows上面测试,生成exe文件后可以直接读入。
c_quick = ctl.load_library(
libname="quick.exe",
loader_path=r"H:\quick\x64\Debug"
)
查看c_quick类型可以发现它是一个<class 'ctypes.CDLL'>
类型。类型是没错的,但是_FuncPtr
函数接收到QuickSort
字符串时,却显示找不到这个函数;如果我们直接使用cpp,或者dll等文件,有显示: 不是有效的win32应用程序。
按照1.4.3案例的写法,可以实现exe软件的加载使用,但是声明部分要添加 __declspec(dllexport)
再次在windows上面测试,使用VS生成dll文件,这个文件是可以加载并使用的,功能正常。代码和案例1.4.3类似,同上所述,只是在声明中使用了
extern "C" __declspec(dllexport) void QuickSort(int A[], int low, int high);
因为我们的执行环境是64位的,而visual studio是基于32位的,理论上有两种办法解决:
- 将python程序改成32位版本,当然这种方法对我们来说一般难以接受;
- 将外部链接编译为64位的,我在studio里面设置了x64,结果设置了一个寂寞。
为了不浪费太多时间,我将这部分搬到Ubuntu系统上进行测试。
1.4 Linux系统下加载编译后的文件
这里我们记录从文件生成到最终调用的全过程。
1.4.1 书写文档
$ sudo vim quik.c
# 将1.1的C++文件内容书写进文档并保持
1.4.2 编译、打包源文件
编译链接为可执行文件
$ sudo g++ -Wall quik.c -o quick
$ ./quick
-6 2 5 8 9
这里对main函数的默认数组进行了排序!
编译为目标文件
$ sudo g++ -Wall -c quik.c -o quick.o
$ ls
quick quick.o quik.c
生成静态链接库
$ sudo ar cr libquick.o quick.o
$ ls
libquick.o quick quick.o quik.c
生成动态链接库
$ sudo g++ -Wall -shared -fPIC quik.c -o libquick.so
$ ls
libquick.o libquick.so quick quick.o quik.c
1.4.3 python加载外部链接库
我们尝试从不同的文件进行加载,分别是c++源文件、可执行文件、.o
目标文件、外部静态链接库、外部动态链接库。
首先说明结果:c++源文件、可执行文件都不能被加载。
加载c++源文件报错:OSError: quik.c: invalid ELF header
加载可执行文件quick报错:OSError: no file with expected extension
加载目标文件报错:OSError: quick.o: only ET_DYN and ET_EXEC can be loaded
加载外部静态链接库报错:OSError: libquick.o: invalid ELF header
加载外部动态链接库报错:undefined symbol:“QuickSort”
现在我们只能使用main函数,其他函数调用会报未定义符号的错误,下面我们先基于so
动态链接库走通这个流程。
1.4.3.1 动态链接库使用成功案例
首先我们模拟正常的项目,进行独立编译。我们将quik文件拆分为quik.c
和main.c
。
问题的解决方案可以参考linux动态库so调用外部so,运行时出现undefined symbol
我们生成如下的quik.c
和main.c
文档:
#include <iostream>
using namespace std;
//extern "C" C++中编译c格式的函数,如果利用c语言编译不需要
extern "C" void QuickSort(int A[], int low, int high);
int Paritition1(int A[], int low, int high) {
int pivot = A[low];
while (low < high) {
while (low < high && A[high] >= pivot) {
--high;
}
A[low] = A[high];
while (low < high && A[low] <= pivot) {
++low;
}
A[high] = A[low];
}
A[low] = pivot;
return low;
}
void QuickSort(int A[], int low, int high) //快排母函数
{
if (low < high) {
int pivot = Paritition1(A, low, high);
QuickSort(A, low, pivot - 1);
QuickSort(A, pivot + 1, high);
}
}
#include <iostream>
using namespace std;
// 声明可以写 在 头文件中
extern "C" void QuickSort(int A[], int low, int high);
int main(){
int arr[5] = { 9, 2, 8, -6, 5 };
QuickSort(arr, 0, 4);
for (int i = 0; i < 4; i++){
cout << arr[i] << " ";
}
cout << arr[4] << endl;
}
上面我们使用extern进行了QuickSort函数的声明,这样就可以不使用quik.h头文件,要注意的是声明中使用的是“C”进行编译防止函数名被优化!基于此项改动就可以得到如下效果
python调用so动态链接库
$ sudo g++ -Wall -c main.c
$ sudo g++ -Wall -c quik.c -o quick.o
$ sudo g++ -Wall -shared -fPIC main.c quik.c -o libquick.so
$ python
>>> import numpy.ctypeslib as ctl
>>> ctl_lib = ctl.load_library("libquick.so", "./")
>>> import numpy as np
>>> arr_1d = ctl.ndpointer(
... dtype=np.int32,
... ndim=1,
... flags="CONTIGUOUS"
... )
>>> ctl_lib.QuickSort.restypes = None
>>> from ctypes import c_int, c_float
>>> ctl_lib.QuickSort.argtypes = [arr_1d, c_int, c_int]
>>> arr1 = np.array([5, -6, 8, 4, 7, 6, 3], dtype=np.int32)
>>> ctl_lib.QuickSort(arr1, 0, len(arr1)-1)
>>> print(arr1)
[-6 3 4 5 6 7 8]
>>> type(arr1)
<class 'numpy.ndarray'>
注: 关于最好还是写在声明头文件中,main.c文件引入头文件;但是函数定义文件中的声明是一定要有的!
动态库可以,静态库却不可以?
直接加载quik.c
源文件生成的静态链接库确实是不行!即使我们已经声明还是会有invalid ELF header
错误,也不难理解,声明是解决不了这个问题的。鉴于我们常使用动态链接库,静态库该如何调用留作悬念吧。不过根据这个报错,很可能我们调用不起来。
1.4.3.2 python调用外部链接库总结
-
调用外部C/C++接口,我们最好使用动态链接库;
-
动态链接库生成的时候一定要注意进行声明
extern "C" void QuickSort(int A[], int low, int high);
不然加载后会找不到函数名,这个名字会被优化成其他
-
更多细节可以参考官方文档https://scipy-cookbook.readthedocs.io/items/Ctypes.html
完!