Как использовать классы С++ с ctypes?

Я только начинаю с ctypes и хотел бы использовать класс С++, который я экспортировал в DLL файл из python, используя ctypes. Итак, скажем, мой код на С++ выглядит примерно так:

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

Я бы хотел создать файл .dll, содержащий этот класс, а затем загрузить DLL файл в python с помощью ctypes. Теперь как я могу создать объект типа MyClass и вызвать его тестовую функцию? Возможно ли это с помощью ctypes? В качестве альтернативы я бы рассмотрел использование SWIG или Boost.Python, но ctypes выглядит как самый простой вариант для небольших проектов.

Ответ 1

Короче говоря, нет стандартного двоичного интерфейса для С++ в том смысле, что существует для C. Различные компиляторы выводят разные двоичные файлы для одних и тех же динамических библиотек С++ из-за изменения имени и различных способов обработки стека между библиотекой вызовы функций.

Итак, к сожалению, действительно нет портативного способа доступа к С++-библиотекам в целом. Но для одного компилятора одновременно это не проблема.

В этом сообщении в блоге также есть краткий обзор того, почему это в настоящее время не будет работать. Может быть, после выхода С++ 0x у нас будет стандартный ABI для С++? До тех пор у вас, вероятно, не будет доступа к классам С++ через Python ctypes.

Ответ 2

Помимо Boost.Python(это, вероятно, более дружественное решение для больших проектов, требующих сопоставления классов С++ для одного класса к классам python), вы можете предоставить на стороне С++ интерфейс C. Это одно из многих решений, поэтому у него есть свои компромиссы, но я представлю его на благо тех, кто не знаком с этой техникой. Для полного раскрытия, при таком подходе один не будет связывать С++ с python, а С++ - с C на Python. Ниже я привел пример, который соответствует вашим требованиям, чтобы показать вам общую идею внешнего компонента "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.

Вы можете скомпилировать этот код с помощью

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

В вашем коде python вы можете сделать что-то вроде этого (интерактивная подсказка из показанного 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] 

Я уверен, что Boost.Python делает что-то похожее под капотом, но, возможно, понимание концепций нижних уровней полезно. Я был бы в восторге от этого метода, если бы вы пытались получить доступ к функциям библиотеки С++, и сопоставление "один к одному" не требовалось.

Для получения дополнительной информации о взаимодействии C/С++ просмотрите эту страницу с Sun: http://dsc.sun.com/solaris/articles/mixing.html#cpp_from_c

Ответ 3

Ответ от AudaAero очень хороший, но не полный (по крайней мере для меня).

В моей системе (Debian Stretch x64 с GCC и g++ 6.3.0, Python 3.5.3) У меня есть segfaults, как только я вызываю функцию-член, которая получает доступ к значению члена класса. Я диагностировал, указав значения указателя на stdout, что указатель void *, закодированный на 64 битах в оболочках, представлен на 32 битах в Python. Таким образом, возникают большие проблемы, когда они передаются обратно в оболочку функции-члена.

Решение, которое я нашел, это изменить:

spam = myLib.CreateInstanceOfClass()

В

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

Итак, две вещи отсутствовали: установка типа возврата в c_void_p (по умолчанию - int) и, а затем создание объекта c_void_p (а не только целое число).

Жаль, что я не мог написать комментарий, но мне все еще не хватает 27 пунктов.

Ответ 5

Расширение AudaAero и Gabriel Devillers ответ. Я бы завершил создание экземпляра объекта класса: stdc=c_void_p(cdll.LoadLibrary("libc.so.6")) используя тип данных ctypes c_void_p, обеспечивает правильное представление указателя объекта класса внутри python.

Также убедитесь, что управление памятью dll обрабатывается dll (выделенная память в dll должна быть освобождена также в dll, а не в python)!