Looking for cython Answers? Try Ask4KnowledgeBase
Looking for cython Keywords? Try Ask4Keywords

cython Обтекание DLL: C ++ до Cython до Python


пример

Это демонстрирует нетривиальный пример переноса C ++ dll с Cython. Он будет охватывать следующие основные этапы:

  • Создайте пример DLL с C ++ с помощью Visual Studio.
  • Оберните DLL с помощью Cython, чтобы он мог быть вызван в Python.

Предполагается, что Cython установлен и может успешно импортировать его в Python.

Для этапа DLL также предполагается, что вы знакомы с созданием DLL в Visual Studio.

Полный пример включает создание следующих файлов:

  1. complexFunLib.h : Заголовочный файл для источника DLL на C ++
  2. complexFunLib.cpp : CPP-файл для источника DLL на C ++
  3. ccomplexFunLib.pxd : файл заголовка Cython
  4. complexFunLib.pyx : файл Cython "wrapper"
  5. setup.py : установочный файл Python для создания complexFunLib.pyd с Cython
  6. run.py : Пример файла Python, который импортирует скомпилированную, обернутую Cython DLL

C ++ DLL Источник: complexFunLib.h и complexFunLib.cpp

Пропустите это, если у вас уже есть файл с исходным кодом DLL и заголовком. Во-первых, мы создаем источник C ++, из которого DLL будет скомпилирована с использованием Visual Studio. В этом случае мы хотим выполнить быстрые вычисления массивов со сложной экспоненциальной функцией. Следующие две функции выполняют вычисление k*exp(ee) на массивах k и ee , где результаты сохраняются в res . Существуют две функции для размещения как одиночной, так и двойной точности. Обратите внимание, что в этих примерных функциях используется OpenMP, поэтому убедитесь, что OpenMP включен в параметрах Visual Studio для проекта.

Файл 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

Затем мы создаем исходные файлы Cython, необходимые для оболочки библиотеки C ++. На этом этапе мы делаем следующие предположения:

  • Вы установили Cython
  • У вас есть рабочая 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 . В этом примере мы будем передавать указатели на объекты Numpy ndarray на функции DLL импорта. Также возможно использовать встроенный объект memoryview Cython для массивов, но его производительность может быть не такой хорошей, как объекты 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

Этот файл представляет собой файл Python, который выполняет компиляцию Cython. Его цель - сгенерировать скомпилированный файл .pyd который затем может быть импортирован модулями Python. В этом примере мы сохранили все необходимые файлы (т.е. complexFunLib.h , complexFunLib.dll , ccomplexFunLib.pxd и complexFunLib.pyx ) в том же каталоге, что и setup.py .

После создания этого файла он должен запускаться из командной строки с параметрами: build_ext --inplace

Как только этот файл будет выполнен, он должен создать файл .pyd без каких-либо ошибок. Обратите внимание, что в некоторых случаях, если есть ошибка, может быть создан .pyd , но недействителен. Убедитесь, что при .pyd setup.py не было ошибок при использовании сгенерированного .pyd .

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)