2009-10-23 28 views
37

Po prostu zaczynam od ctypes i chciałbym użyć klasy C++, które wyeksportowałem w pliku dll z poziomu Pythona za pomocą ctypes. Więc powiedzmy, że mój kod C++ wygląda mniej więcej tak:Jak korzystać z klas C++ z ctypes?

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

wiedziałbym utworzyć .dll plik zawierający tę klasę, a następnie załadować plik .dll w Pythonie przy użyciu ctypes. Teraz, jak utworzyć obiekt typu MyClass i zadzwonić do jego funkcji testowej? Czy to możliwe nawet przy użyciu ctypes? Alternatywnie rozważałbym użycie SWIG lub Boost.Python, ale ctypes wydaje się być najłatwiejszą opcją dla małych projektów.

Odpowiedz

32

Krótko mówiąc, nie ma standardowego interfejsu binarnego dla C++ w sposób jaki jest dla C. Różne kompilatory wyprowadzają różne pliki binarne dla tych samych bibliotek dynamicznych C++, ze względu na wymazywanie nazw i różne sposoby obsługi stosu między wywołania funkcji biblioteki.

Niestety, tak naprawdę nie istnieje przenośny sposób uzyskiwania dostępu do bibliotek C++ ogólnie. Ale dla jednego kompilatora na raz, nie ma problemu.

This blog post ma również krótki przegląd tego, dlaczego obecnie nie będzie działać. Może po wyjściu C++ 0x otrzymamy standardową ABI dla C++? Do tego czasu prawdopodobnie nie będziesz mieć dostępu do klas C++ za pośrednictwem Pythona ctypes.

33

Poza Boost.Python (który jest prawdopodobnie bardziej przyjaznym rozwiązaniem dla większych projektów, które wymagają odwzorowania klasy C++ typu "jeden do jednego" na klasy Pythona), można podać po stronie C++ interfejs C. Jest to jedno rozwiązanie wielu, więc ma własne kompromisy, ale przedstawię je dla korzyści tych, którzy nie znają tej techniki. Dla pełnego ujawnienia, przy takim podejściu nie byłby to interfejs C++ do Pythona, ale C++ do C do Pythona. Poniżej zamieściłem przykład, który spełnia twoje wymagania, aby pokazać ogólną ideę zewnętrznego "c" obiektu kompilatorów 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. 

można skompilować ten kod z

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

W kodzie Pythona można zrobić coś takiego (Interactive wierszu od 2,7 pokazany):

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

Jestem pewien doładowania .Python robi coś podobnego pod maską, ale być może zrozumienie koncepcji niższego poziomu jest pomocne. Byłbym bardziej podekscytowany tą metodą, jeśli próbujesz uzyskać dostęp do funkcjonalności biblioteki C++, a mapowanie jeden-do-jednego nie było wymagane.

Więcej informacji na temat C/C++ interakcji sprawdzić tę stronę od Słońca: http://dsc.sun.com/solaris/articles/mixing.html#cpp_from_c

+5

Rzeczywiście, jest to trochę uciążliwe, ale działa. Będziesz jednak szczególnie uważać na wyjątki. Nie sądzę, że można bezpiecznie założyć, że moduł 'ctypes' obsługuje funkcje C, które bardzo dobrze wyrzucają wyjątki. W szczególności instrukcja 'return new MyClass;' jest bardzo niebezpieczna, ponieważ może wywołać 'std :: bad_alloc'. –

+2

Należy również dodać funkcję 'DestroyInstanceOfClass()'. –

+0

To bardzo dobry punkt. Będę edytować przykład, aby użyć wariantu bez rzutowania. Inną sztuczką byłoby przechwycenie wszystkich wyjątków w bloku try w ciele C++ funkcji. – AudaAero

0

To krótkie wyjaśnienie, w jaki sposób korzystać z C i C++ z C_types w Pythonie. How to write a DLL/SO in C++ for Python

+4

-1. Pytanie dotyczy klas C++ *, ale ten link daje tylko przykład darmowej funkcji. Także odpowiedzi, które nie zawierają żadnych faktycznych informacji innych niż adres URL, powinny być publikowane jako komentarze. – JBentley

3

answer by AudaAero jest bardzo dobry, ale nie kompletny (przynajmniej dla mnie).

W moim systemie (Debian Stretch x64 z GCC i G ++ 6.3.0, Python 3.5.3) Mam segfault, jak tylko zadzwonię do funkcji członka, która uzyskuje dostęp do wartości członka klasy. Zostałem zdiagnozowany przez drukowanie wartości wskaźników na standardowe wyjście, że void * wskaźnik zakodowany na 64 bitach w owijkach jest reprezentowany na 32 bitach w Pythonie.Tak więc duże problemy występują, gdy są przekazywane z powrotem do wrappera funkcji członka.

Rozwiązanie znalazłem to zmienić:

spam = myLib.CreateInstanceOfClass() 

Do

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

więc dwie rzeczy brakowało: ustawienie typu powrotną do c_void_p (domyślnie jest to int) i następnie tworzenie obiekt c_void_p (nie tylko liczba całkowita).

Chciałbym móc napisać komentarz, ale nadal brakuje mi 27 punktów rep.

0

Rozszerzanie AudaAero's i Gabriel Devillers odpowiedź chciałbym zakończyć tworzenie instancji klasy obiektu przez: stdc=c_void_p(cdll.LoadLibrary("libc.so.6")) użyciu ctypesc_void_p typ danych zapewnia właściwą reprezentację wskaźnika klasy obiekt w Pythonie.

Upewnij się także, że zarządzanie pamięcią biblioteki DLL jest obsługiwane przez bibliotekę DLL (przydzielona pamięć w bibliotece dll powinna zostać zwolniona również w bibliotece dll, a nie w pythonie)!