web-dev-qa-db-fra.com

Comment utiliser les classes C++ avec les ctypes?

Je commence tout juste avec les ctypes et j'aimerais utiliser une classe C++ que j'ai exportée dans un fichier dll à partir de python en utilisant ctypes .

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

Je voudrais savoir créer un fichier .dll contenant cette classe, puis charger le fichier .dll en python à l'aide de ctypes . Comment créer un objet de type MyClass et appeler sa fonction test? Est-ce même possible avec des types? Alternativement, je considérerais d’utiliser SWIG ou Boost.Python mais ctypes semble être l’option la plus simple pour les petits projets.

46
jörg

En résumé, il n’existe pas d’interface binaire standard pour C++, contrairement à C. Les différents compilateurs génèrent différents binaires pour les mêmes bibliothèques dynamiques C++, en raison de la gestion des noms et des différentes manières de gérer la pile entre les appels de fonctions des bibliothèques.

Donc, malheureusement, il n’existe vraiment pas de moyen portable d’accéder aux bibliothèques C++ en général. Mais, pour un compilateur à la fois, ce n'est pas un problème. 

Cet article de blog présente également un bref aperçu des raisons pour lesquelles cela ne fonctionne pas actuellement. Peut-être qu'après la sortie de C++ 0x, nous aurons un ABI standard pour C++? Jusque-là, vous n'aurez probablement aucun moyen d'accéder aux classes C++ via la variable ctypes de Python.

36
Mark Rushakoff

Outre Boost.Python (qui est probablement une solution plus conviviale pour les projets plus importants qui nécessitent un mappage un à un des classes C++ en classes python), vous pouvez fournir une interface C côté C++. C’est une solution parmi tant d’autres, elle a donc ses propres compromis, mais je la présenterai au profit de ceux qui ne sont pas familiarisés avec la technique. Pour une divulgation complète, avec cette approche, on n’interfacerait pas C++ avec Python, mais entre C++ et C vers Python. Ci-dessous, j’ai inclus un exemple qui répond à vos besoins pour vous montrer l’idée générale de la fonction extern "c" des compilateurs 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.

Vous pouvez compiler ce code avec 

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

Dans votre code python, vous pouvez faire quelque chose comme ceci (invite interactive à partir de 2.7 affichée):

>>> 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] 

Je suis sûr que Boost.Python fait quelque chose de similaire sous le capot, mais peut-être comprendre les concepts des niveaux inférieurs est utile. Je serais plus enthousiaste à propos de cette méthode si vous tentiez d'accéder aux fonctionnalités d'une bibliothèque C++ et qu'un mappage un à un n'était pas nécessaire. 

Pour plus d'informations sur l'interaction C/C++, consultez cette page de Sun: http://dsc.Sun.com/solaris/articles/mixing.html#cpp_from_c

37
AudaAero

Le réponse de AudaAero est très bon mais pas complet (du moins pour moi).

Sur mon système (Debian Stretch x64 avec GCC et G ++ 6.3.0, Python 3.5.3), j'ai des erreurs de segmentation dès que j'ai appelé une fonction membre qui accède à une valeur membre de la classe . J'ai diagnostiqué par impression des valeurs de pointeur sur stdout indiquant que le pointeur void * codé sur 64 bits dans les wrappers est représenté sur 32 bits en Python. Ainsi, de gros problèmes surviennent quand il est renvoyé à un wrapper de fonction membre.

La solution que j'ai trouvée est de changer:

spam = myLib.CreateInstanceOfClass()

Dans

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

Il manquait donc deux choses: définir le type de retour sur c_void_p (la valeur par défaut est int) et, puis créer un objet c_void_p (pas uniquement un entier).

J'aurais aimé pouvoir écrire un commentaire, mais il me manque encore 27 points de rep.

5

Voici une brève explication de la manière d'utiliser c et c ++ avec C_types en python . Comment écrire une DLL/SO en C++ pour Python

0
ifryed

Extension de AudaAero et Gabriel Devillers answer Je compléterais la création de l'instance d'objet de classe par: stdc=c_void_p(cdll.LoadLibrary("libc.so.6")) En utilisant ctypesc_void_p garantit la représentation correcte du pointeur d'objet de classe dans python.

Assurez-vous également que la gestion de la mémoire de la DLL est gérée par la DLL (la mémoire allouée dans la DLL doit être désallouée également dans la DLL, et non en python)!

0
Elektrone Motyo