Pregunta

Estoy empezando con ctypes y me gustaría usar una clase de C ++ que haya exportado en un archivo dll desde python usando ctypes. Así que digamos que mi código C ++ se parece a esto:

class MyClass {
  public:
    int test();
...

Yo sabría crear un archivo .dll que contenga esta clase y luego cargar el archivo .dll en Python usando ctypes. Ahora, ¿cómo crear un objeto de tipo MyClass y llamar a su función de prueba? ¿Es eso posible con ctypes? Alternativamente, consideraría usar SWIG o Boost.Python pero ctypes parece ser la opción más fácil para proyectos pequeños.

¿Fue útil?

Solución

El cuento es que no hay una interfaz binaria estándar para C ++ de la forma en que lo hay para C. Los diferentes compiladores generan binarios diferentes para las mismas bibliotecas dinámicas de C ++, debido a la manipulación de nombres y las diferentes formas de manejar la pila entre bibliotecas Llamadas de función.

Desafortunadamente, realmente no hay una forma portátil de acceder a las bibliotecas de C ++ en general . Pero, para un compilador a la vez, no hay problema.

Esta publicación del blog también tiene una breve descripción de por qué esto no funciona actualmente. Quizás después de que salga C ++ 0x, ¿tendremos un ABI estándar para C ++? Hasta entonces, es probable que no tenga forma de acceder a las clases de C ++ a través de ctypes de Python.

Otros consejos

Además de Boost.Python (que probablemente sea una solución más amigable para proyectos más grandes que requieren un mapeo uno a uno de clases de C ++ a clases de python), podría proporcionar una interfaz C en el lado de C ++. Es una solución para muchos, por lo que tiene sus propias compensaciones, pero la presentaré en beneficio de aquellos que no están familiarizados con la técnica. Para una revelación completa, con este enfoque, uno no estaría interconectando C ++ con python, sino C ++ con C to Python. A continuación, incluí un ejemplo que cumple con sus requisitos para mostrarle la idea general de extern " c " Instalación de compiladores de C ++.

//YourFile.cpp (compiled into a .dll or .so file)
#include <new> //For std::nothrow
//Either include a header defining your class, or define it here. 

extern "C"  //Tells the compile to use C-linkage for the next scope.
{
    //Note: The interface this linkage region needs to use C only.  
    void * CreateInstanceOfClass( void )
    {
        // Note: Inside the function body, I can use C++. 
        return new(std::nothrow) MyClass;
    }

    //Thanks Chris. 
    void DeleteInstanceOfClass (void *ptr)
    {
         delete(std::nothrow) ptr; 
    }

    int CallMemberTest(void *ptr)
    {

        // Note: A downside here is the lack of type safety. 
        // You could always internally(in the C++ library) save a reference to all 
        // pointers created of type MyClass and verify it is an element in that
        //structure. 
        //
        // Per comments with Andre, we should avoid throwing exceptions.  
        try
        {
            MyClass * ref = reinterpret_cast<MyClass *>(ptr);
            return ref->Test();
        }
        catch(...)
        {
           return -1; //assuming -1 is an error condition. 
        }
    }

} //End C linkage scope.

Puedes compilar este código con

gcc -shared -o test.so test.cpp
#creates test.so in your current working directory.

En tu código de Python, puedes hacer algo como esto (se muestra una solicitud interactiva de 2.7):

>>> from ctypes import cdll
>>> stdc=cdll.LoadLibrary("libc.so.6") # or similar to load c library
>>> stdcpp=cdll.LoadLibrary("libstdc++.so.6") # or similar to load c++ library
>>> myLib=cdll.LoadLibrary("/path/to/test.so")
>>> spam = myLib.CreateInstanceOfClass()
>>> spam
[outputs the pointer address of the element]
>>> value=CallMemberTest(spam)
[does whatever Test does to the spam reference of the object] 

Estoy seguro de que Boost.Python hace algo similar bajo el capó, pero tal vez sea útil comprender los conceptos de niveles más bajos. Estaría más entusiasmado con este método si intentara acceder a la funcionalidad de una biblioteca C ++ y no se requirió un mapeo uno a uno.

Para obtener más información sobre la interacción C / C ++, visite esta página de Sun: http : //dsc.sun.com/solaris/articles/mixing.html#cpp_from_c

La respuesta de AudaAero es muy buena pero no está completa (al menos para mí).

En mi sistema (Debian Stretch x64 con GCC y G ++ 6.3.0, Python 3.5.3) Tengo segfaults tan pronto como llamo a una función miembro que accede a un valor miembro de la clase. Diagnosticé imprimiendo valores de puntero para indicar que el puntero void * codificado en 64 bits en envoltorios se representa en 32 bits en Python. Por lo tanto, se producen grandes problemas cuando se devuelve a un envoltorio de función miembro.

La solución que encontré es cambiar:

spam = myLib.CreateInstanceOfClass()

En

Class_ctor_wrapper = myLib.CreateInstanceOfClass
Class_ctor_wrapper.restype = c_void_p
spam = c_void_p(Class_ctor_wrapper())

Faltaban dos cosas: establecer el tipo de retorno en c_void_p (el valor predeterminado es int) y y luego crear un objeto c_void_p (no solo un entero).

Desearía haber escrito un comentario pero aún me faltan 27 puntos de repetición.

Esta es una breve explicación sobre cómo usar c y c ++ con C_types en python. Cómo escribir una DLL / SO en C ++ para Python

Ampliación de AudaAero's y Respuesta de Gabriel Devillers completaría la creación de la instancia de objeto de clase de la siguiente manera: stdc = c_void_p (cdll.LoadLibrary (" libc.so.6 ")) el uso del tipo de datos ctypes c_void_p garantiza la representación correcta del puntero del objeto de clase dentro de python.

¡También asegúrate de que la administración de memoria de la dll sea manejada por la dll (la memoria asignada en la dll también debe ser desasignada en la dll, y no en python)!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top