ctypes与numpy.ctypeslib的使用

numpy ctypeslib 与 ctypes接口使用说明


作者:elfin  


使用numpy.ctypelib或者ctypes库可以实现python直接待用C++。numpy.ctypeslib后端是基于ctypes实现的!所以接口是类似的。

如果只想看如何调用外部接口可以直接查看 1.4.3.1 动态链接库使用成功案例 ,其他部分有很多试错的过程!

Top --- Bottom


一、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_arrayc++数据转为numpy数据类型

Top --- Bottom

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

Top --- Bottom

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.cmain.c

问题的解决方案可以参考linux动态库so调用外部so,运行时出现undefined symbol

我们生成如下的quik.cmain.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


Top --- Bottom

完!

posted @ 2021-12-11 16:47  巴蜀秀才  阅读(1284)  评论(0编辑  收藏  举报