cython 包装DLL:从C ++到Cython到Python

示例

这展示了一个用Cython包装C ++ dll的简单例子。它将涵盖以下主要步骤:

  • 使用Visual Studio使用C ++创建示例DLL。

  • 用Cython包裹DLL,以便可以在Python中调用它。

假定您已安装Cython,并可以在Python中成功导入它。

对于DLL步骤,还假定您熟悉在Visual Studio中创建DLL。

完整的示例包括以下文件的创建:

  1. complexFunLib.h:C ++ DLL源的头文件

  2. complexFunLib.cpp:C ++ DLL源的CPP文件

  3. ccomplexFunLib.pxd:Cython“头文件”

  4. complexFunLib.pyx:Cython“包装器”文件

  5. setup.py:complexFunLib.pyd使用Cython创建的Python安装文件

  6. run.py:导入已编译的Cython包装的DLL的示例Python文件

C ++ DLL来源:complexFunLib.h和complexFunLib.cpp

如果您已经有了DLL和标头源文件,请跳过此步骤。首先,我们创建C ++源,使用Visual Studio从中编译DLL。在这种情况下,我们要使用复杂的指数函数进行快速数组计算。以下两个函数对数组and进行计算,结果存储在中。有两种功能可同时满足单精度和双精度。请注意,这些示例函数使用OpenMP,因此请确保在项目的Visual Studio选项中启用了OpenMP。k*exp(ee)keeres

H档

// Avoids C++ name mangling with extern "C"
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)  
#include <complex>
#include <stdlib.h>

// 处理64位复数,即两个32位(4字节)浮点数
EXTERN_DLL_EXPORT void mp_mlt_exp_c4(std::complex<float>* k, 
                                     std::complex<float>* ee,
                                     int sz, 
                                     std::complex<float>* res, 
                                     int threads);

// 处理128位复数,即两个64位(8字节)浮点数
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)
{
    // 使用Open MP并行指令进行多处理
    #pragma omp并行num_threads(线程)
    {
        #编译指示
        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)
{
    // 使用Open MP并行指令进行多处理
    #pragma omp并行num_threads(线程)
    {
        #编译指示
        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复合体类型。请注意c在开头添加ccomplexFunLib.pxd。这不是必需的,但是我们发现这样的命名约定有助于维护组织。

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源文件。在此示例中,我们将指向Numpyndarray对象的指针传递给导入DLL函数。也可以将内置的Cythonmemoryview对象用于数组,但是其性能可能不如ndarray对象(但是语法明显更清晰)。

cimport ccomplexFunLib  # Import the pxd "header"
# 对于Numpy导入,请注意,C导入大多数在Python导入之后
import numpy as np  # 导入Python Numpy
cimport numpy as np  # 导入C块

# 从Python和C stdlib导入一些功能
fromcpython.pycapsulecimport *

# Python包装函数。
# 请注意,可以在签名中添加类型

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
    '''
    # 在参数上调用导入的DLL函数。
    # 注意,我们正在传递一个指向每个数组中第一个元素的指针
    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可能会创建,但无效。setup.py使用生成的之前,请确保在执行时不会抛出任何错误.pyd。

fromdistutils.coreimport setup
fromdistutils.extensionimport Extension
fromCython.Distutilsimport build_ext
import numpy as np

ext_modules = [
    Extension('complexFunLib',
              ['complexFunLib.pyx'],
              # 请注意,此处指定了C ++语言
              # 默认语言是C
              language="c++",  
              libraries=['complexFunLib'],
              library_dirs=['.'])
    ]

setup(
    name = 'complexFunLib',
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules,
    include_dirs=[np.get_include()]  # 这将获取所有必需的Numpy核心文件
)

运行

现在complexFunLib可以直接导入到Python模块中,并调用包装的DLL函数。

import complexFunLib
import numpy as np

# 创建要求幂的非平凡复数数组,
# 即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 # 获取大小整数
res = np.zeros(int(2.5e5), dtype='complex64')  # 创建结果数组

# 通话功能
complexFunLib.mp_exp_c4(k, ee, sz, res, 8)  

# 打印结果
print(res)