將 DLL C 包裝到 Cython 到 Python
這演示了使用 Cython 包裝 C++ dll 的一個非平凡的例子。它將涵蓋以下主要步驟:
- 使用 Visual Studio 使用 C++建立示例 DLL。
- 用 Cython 包裝 DLL,以便可以在 Python 中呼叫它。
假設你已安裝 Cython 並可以在 Python 中成功匯入它
對於 DLL 步驟,還假設你熟悉在 Visual Studio 中建立 DLL。
完整示例包括建立以下檔案:
complexFunLib.h
:C++ DLL 原始碼的標頭檔案complexFunLib.cpp
:C++ DLL 源的 CPP 檔案ccomplexFunLib.pxd
:Cythonheader
檔案complexFunLib.pyx
:Cython包裝檔案setup.py
:使用 Cython 建立complexFunLib.pyd
的 Python 設定檔案run.py
:匯入已編譯的 Cython 包裝 DLL 的示例 Python 檔案
C++ DLL 來源:complexFunLib.h
和 complexFunLib.cpp
**如果你已有 DLL 和標頭原始檔,請跳過此步驟。**首先,我們建立 C++原始碼,使用 Visual Studio 從中編譯 DLL。在這種情況下,我們希望使用復指數函式進行快速陣列計算。以下兩個函式在陣列 k
和 ee
上執行計算 k*exp(ee)
,其中結果儲存在 res
中。有兩個功能可以相容單精度和雙精度。請注意,這些示例函式使用 OpenMP,因此請確保在專案的 Visual Studio 選項中啟用了 OpenMP。
H 檔案
// Avoids C++ name mangling with extern "C"
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
#include <complex>
#include <stdlib.h>
// Handles 64 bit complex numbers, i.e. two 32 bit (4 byte) floating point numbers
EXTERN_DLL_EXPORT void mp_mlt_exp_c4(std::complex<float>* k,
std::complex<float>* ee,
int sz,
std::complex<float>* res,
int threads);
// Handles 128 bit complex numbers, i.e. two 64 bit (8 byte) floating point numbers
EXTERN_DLL_EXPORT void mp_mlt_exp_c8(std::complex<double>* k, std::complex<double>* ee,
int sz,
std::complex<double>* res,
int threads);
CPP 檔案
#include "stdafx.h"
#include <stdio.h>
#include <omp.h>
#include "complexFunLib.h"
void mp_mlt_exp_c4(std::complex<float>* k,
std::complex<float>* ee,
int sz,
std::complex<float>* res,
int threads)
{
// Use Open MP parallel directive for multiprocessing
#pragma omp parallel num_threads(threads)
{
#pragma omp for
for (int i = 0; i < sz; i++) res[i] = k[i] * exp(ee[i]);
}
}
void mp_mlt_exp_c8(std::complex<double>* k,
std::complex<double>* ee,
int sz, std::complex<double>* res,
int threads)
{
// Use Open MP parallel directive for multiprocessing
#pragma omp parallel num_threads(threads)
{
#pragma omp for
for (int i = 0; i < sz; i++) res[i] = k[i] * exp(ee[i]);
}
}
Cython 來源:ccomplexFunLib.pxd
和 complexFunLib.pyx
接下來,我們建立包裝 C++ DLL 所必需的 Cython 原始檔。在這一步中,我們做出以下假設:
- 你已經安裝了 Cython
- 你擁有一個可用的 DLL,例如上面描述的 DLL
最終目標是建立將這些 Cython 原始檔與原始 DLL 結合使用來編譯 .pyd
檔案,該檔案可以作為 Python 模組匯入並公開用 C++編寫的函式。
PXD 檔案
該檔案對應 C++標頭檔案。在大多數情況下,你可以使用較小的 Cython 特定更改將標頭複製貼上到此檔案。在這種情況下,使用特定的 Cython 複雜型別。注意在 ccomplexFunLib.pxd
開頭新增 c
。這不是必需的,但我們發現這樣的命名約定有助於維護組織。
cdef extern from "complexFunLib.h":
void mp_mlt_exp_c4(float complex* k, float complex* ee, int sz,
float complex* res, int threads);
void mp_mlt_exp_c8(double complex* k, double complex* ee, int sz,
double complex* res, int threads);
PYX 檔案
該檔案對應於 C++ cpp
原始檔。在這個例子中,我們將指向 Numpy ndarray
物件的指標傳遞給匯入 DLL 函式。也可以將內建的 Cython memoryview
物件用於陣列,但其效能可能不如 ndarray
物件(但語法非常清晰)。
cimport ccomplexFunLib # Import the pxd "header"
# Note for Numpy imports, the C import most come AFTER the Python import
import numpy as np # Import the Python Numpy
cimport numpy as np # Import the C Numpy
# Import some functionality from Python and the C stdlib
from cpython.pycapsule cimport *
# Python wrapper functions.
# Note that types can be delcared in the signature
def mp_exp_c4(np.ndarray[np.complex64_t, ndim=1] k,
np.ndarray[np.complex64_t, ndim=1] ee,
int sz,
np.ndarray[np.complex64_t, ndim=1] res,
int threads):
'''
TODO: Python docstring
'''
# Call the imported DLL functions on the parameters.
# Notice that we are passing a pointer to the first element in each array
ccomplexFunLib.mp_mlt_exp_c4(&k[0], &ee[0], sz, &res[0], threads)
def mp_exp_c8(np.ndarray[np.complex128_t, ndim=1] k,
np.ndarray[np.complex128_t, ndim=1] ee,
int sz,
np.ndarray[np.complex128_t, ndim=1] res,
int threads):
'''
TODO: Python docstring
'''
ccomplexFunLib.mp_mlt_exp_c8(&k[0], &ee[0], sz, &res[0], threads)
Python 來源:setup.py
和 run.py
setup.py
該檔案是一個執行 Cython 編譯的 Python 檔案。其目的是生成已編譯的 .pyd
檔案,然後由 Python 模組匯入。在這個例子中,我們將所有必需的檔案(即 complexFunLib.h
,complexFunLib.dll
,ccomplexFunLib.pxd
和 complexFunLib.pyx
)儲存在與 setup.py
相同的目錄中。
建立此檔案後,應從命令列執行引數:build_ext --inplace
執行此檔案後,它應生成 .pyd
檔案而不會引發任何錯誤。請注意,在某些情況下,如果出現錯誤,可能會建立 .pyd
但無效。在使用生成的 .pyd
之前,確保在執行 setup.py
時沒有丟擲任何錯誤。
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy as np
ext_modules = [
Extension('complexFunLib',
['complexFunLib.pyx'],
# Note here that the C++ language was specified
# The default language is C
language="c++",
libraries=['complexFunLib'],
library_dirs=['.'])
]
setup(
name = 'complexFunLib',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules,
include_dirs=[np.get_include()] # This gets all the required Numpy core files
)
run.py
現在 complexFunLib
可以直接匯入 Python 模組並呼叫包裝的 DLL 函式。
import complexFunLib
import numpy as np
# Create arrays of non-trivial complex numbers to be exponentiated,
# i.e. res = k*exp(ee)
k = np.ones(int(2.5e5), dtype='complex64')*1.1234 + np.complex64(1.1234j)
ee = np.ones(int(2.5e5), dtype='complex64')*1.1234 + np.complex64(1.1234j)
sz = k.size # Get size integer
res = np.zeros(int(2.5e5), dtype='complex64') # Create array for results
# Call function
complexFunLib.mp_exp_c4(k, ee, sz, res, 8)
# Print results
print(res)