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)
因為我們的執行環境是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
完!