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
完!