Ricerca…


Wrapping di una DLL: da C ++ a Cython a Python

Questo dimostra un esempio non banale di wrapping di una dll C ++ con Cython. Coprirà i seguenti passaggi principali:

  • Creare un esempio DLL con C ++ utilizzando Visual Studio.
  • Avvolgi la DLL con Cython in modo che possa essere chiamata in Python.

Si presume che Cython sia installato e possa importarlo con successo in Python.

Per il passaggio della DLL, si presume anche che si abbia familiarità con la creazione di una DLL in Visual Studio.

L'esempio completo include la creazione dei seguenti file:

  1. complexFunLib.h : file di intestazione per l'origine DLL C ++
  2. complexFunLib.cpp : file CPP per l'origine DLL C ++
  3. ccomplexFunLib.pxd : file "header" di Cython
  4. complexFunLib.pyx : file "wrapper" di Cython
  5. setup.py : file di installazione di Python per la creazione di complexFunLib.pyd con Cython
  6. run.py : esempio di file Python che importa la DLL compilata, compilata con Cython

C ++ DLL Origine: complexFunLib.h e complexFunLib.cpp

Salta questo se hai già una DLL e un file sorgente di intestazione. Per prima cosa, creiamo il sorgente C ++ da cui la DLL verrà compilata usando Visual Studio. In questo caso, vogliamo eseguire calcoli di array veloci con la complessa funzione esponenziale. Le seguenti due funzioni eseguono il calcolo k*exp(ee) sugli array k ed ee , dove i risultati sono memorizzati in res . Ci sono due funzioni per ospitare sia la precisione singola che quella doppia. Nota che queste funzioni di esempio usano OpenMP, quindi assicurati che OpenMP sia abilitato nelle opzioni di Visual Studio per il progetto.

H File

// 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);

File 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]);
    }
}

Origine Cython: ccomplexFunLib.pxd e complexFunLib.pyx

Successivamente, creiamo i file sorgente Cython necessari per avvolgere la DLL C ++. In questo passaggio, facciamo le seguenti ipotesi:

  • Hai installato Cython
  • Hai una DLL funzionante, ad esempio quella descritta sopra

L'obiettivo finale è creare questi file sorgente Cython in combinazione con la DLL originale per compilare un file .pyd che può essere importato come un modulo Python ed espone le funzioni scritte in C ++.

File PXD

Questo file corrisponde al file di intestazione C ++. Nella maggior parte dei casi, è possibile copiare e incollare l'intestazione su questo file con modifiche specifiche di Cython minori. In questo caso, sono stati utilizzati i tipi di complessi Cython specifici. Notare l'aggiunta di c all'inizio di ccomplexFunLib.pxd . Questo non è necessario, ma abbiamo scoperto che una tale convenzione di denominazione aiuta a mantenere l'organizzazione.

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

File PYX

Questo file corrisponde al file di origine c ++ cpp . In questo esempio, passeremo i puntatori agli oggetti Numpy ndarray alle funzioni DLL di importazione. È anche possibile utilizzare l'oggetto built in Cython memoryview per gli array, ma le sue prestazioni potrebbero non essere buone come ndarray oggetti ndarray (tuttavia la sintassi è significativamente più pulita).

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)

Origine Python: setup.py e run.py

setup.py

Questo file è un file Python che esegue la compilazione di Cython. Il suo scopo è generare il file .pyd compilato che possa essere importato dai moduli Python. In questo esempio, abbiamo mantenuto tutti i file richiesti (ad esempio complexFunLib.h , complexFunLib.dll , ccomplexFunLib.pxd e complexFunLib.pyx ) nella stessa directory di setup.py .

Una volta creato questo file, dovrebbe essere eseguito dalla riga di comando con i parametri: build_ext --inplace

Una volta eseguito questo file, dovrebbe produrre un file .pyd senza generare errori. Nota che in alcuni casi, se c'è un errore, il file .pyd può essere creato ma non è valido. Assicurarsi che non siano stati generati errori nell'esecuzione di setup.py prima di utilizzare il file .pyd generato.

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

Ora complexFunLib può essere importato direttamente in un modulo Python e richiamate le funzioni DLL avvolte.

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)


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow