将 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)