Как использовать классы C ++ с ctypes? - PullRequest
50 голосов
/ 24 октября 2009

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

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

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

Ответы [ 5 ]

37 голосов
/ 15 августа 2011

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

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

37 голосов
/ 24 октября 2009

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

Так что, к сожалению, на самом деле нет портативного способа доступа к библиотекам C ++ в общем . Но для одного компилятора за раз это не проблема.

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

5 голосов
/ 05 марта 2017

Ответ от 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 точек повторения.

0 голосов
/ 01 августа 2017

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

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

0 голосов
/ 16 февраля 2014

Это краткое объяснение того, как использовать c и c ++ с C_types в python. Как написать DLL / SO на C ++ для Python

...