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

完!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM