介紹如何在 Python 中使用 ctypes
載入 DLL 與 SO 動態連結函式庫,搭配 C/C++ 語言進行高速計算。
Python 的 ctypes
模組可以用來呼叫外部的函式庫,提供相容於 C 語言的資料型別,開發者可以透過這個模組直接呼叫 DLL 檔案(Windows 平台)或 SO 檔案(macOS 或 Linux 平台)中的函數。以下分別介紹 Windows 平台建立與使用 DLL 檔案、macOS 與 Linux 平台建立與使用 SO 檔案的步驟。
在 Windows 平台中可將 C/C++ 語言的程式編譯成 DLL 動態連結函式庫,再以 Python 的 ctypes
載入使用。
若在 Windows 中要建立 DLL 動態連結函式庫,可以使用 Visual Studio 開發環境來建立 DLL 檔案,這部分可以參考 Visual Studio 2019 建立 DLL 動態連結函式庫教學與範例,而在編譯時要注意編譯的版本(32 位元或 64 位元)要跟 Python 的版本相同。
準備好 MyDLL.dll
這個動態連結函式庫檔案之後,就可以在 Python 中利用 ctypes
載入並呼叫其中的函數了。
from ctypes import cdll, c_double # 載入 DLL 動態連結函式庫 mydll = cdll.LoadLibrary('./MyDLL.dll') # 設定 DLL 檔案中 dist 函數的參數資料型態 mydll.dist.argtypes = [c_double, c_double, c_double, c_double] # 設定 DLL 檔案中 dist 函數的傳回值資料型態 mydll.dist.restype = c_double # 呼叫 DLL 檔案中的 dist 函數 d = mydll.dist(1.0, 1.0, 4.0, 5.0) print("dist =", d)
dist = 5.0
若在 macOS 與 Linux 平台中,則會改用 gcc
或 g++
將 C/C++ 的程式編譯成 SO 動態連結檔,再以 Python 的 ctypes
載入使用。
以 C++ 撰寫一個計算歐氏距離(Euclidean Distance)的函數,原始碼如下:
#include <cmath> // 計算歐氏距離函數 extern "C" double dist( const double x1, const double y1, const double x2, const double y2) { return std::sqrt(std::pow(x1 - x2, 2) + std::pow(y1 - y2, 2)); }
將這段 C++ 程式碼儲存為 my_so.cpp
,並以 g++
將其編譯成 SO 動態連結檔案:
# 將 C++ 程式編譯成 SO 動態連結檔案 g++ -Wall -Wextra -O -ansi -pedantic -fPIC -shared my_so.cpp -o my_so.so
編譯成功之後,就會產生 my_so.so
這個動態連結檔案。
準備好 my_so.so
這個動態連結檔案之後,就可以在 Python 中利用 ctypes
載入並呼叫其中的函數了。
from ctypes import cdll, c_double # 載入 SO 動態連結檔案 mydll = cdll.LoadLibrary('./my_so.so') # 設定 SO 檔案中 dist 函數的參數資料型態 mydll.dist.argtypes = [c_double, c_double, c_double, c_double] # 設定 SO 檔案中 dist 函數的傳回值資料型態 mydll.dist.restype = c_double # 呼叫 SO 檔案中的 dist 函數 d = mydll.dist(1.0, 1.0, 4.0, 5.0) print("dist =", d)
dist = 5.0
若需要在 Python 與 C/C++ 之間傳遞整個陣列的資料,可以使用指標的方式,以下是一個簡單的 SO 動態連結檔案範例。
假設 C++ 的程式碼如下,sort
函數會將傳入的陣列進行排序:
#include <algorithm> #include <functional> // 排序函數 extern "C" void sort( double array[], const unsigned int size) { std::sort(array, array + size, std::less<double>()); }
將這段 C++ 程式碼編譯成 my_so2.so
動態連結檔案之後,在 Python 中使用以下方式傳入 NumPy 陣列即可進行資料的排序。
import numpy as np from ctypes import cdll, c_double, c_uint, POINTER # 載入 SO 動態連結檔案 mydll = cdll.LoadLibrary('./my_so2.so') # 設定 SO 檔案中 sort 函數的參數資料型態 mydll.sort.argtypes = [POINTER(c_double), c_uint] # 設定 SO 檔案中 sort 函數的傳回值資料型態 mydll.sort.restype = None # 建立 NumPy 資料陣列 data = np.array([3.2, 5.6, 1.2, 9.4, 7.3]) # 取得陣列的指標 dataPtr = data.ctypes.data_as(POINTER(c_double)) # 呼叫 SO 檔案中的 sort 函數 mydll.sort(dataPtr, data.size) print(data)
[1.2 3.2 5.6 7.3 9.4]
參考資料:Wolfprojects