Как использовать классы С++ с 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 пунктов.
Ответ 4
Это краткое объяснение того, как использовать c и С++ с C_types в python.
Как написать DLL/SO в С++ для Python
Ответ 5
Расширение AudaAero и Gabriel Devillers ответ. Я бы завершил создание экземпляра объекта класса:
stdc=c_void_p(cdll.LoadLibrary("libc.so.6"))
используя тип данных ctypes
c_void_p
, обеспечивает правильное представление указателя объекта класса внутри python.
Также убедитесь, что управление памятью dll обрабатывается dll (выделенная память в dll должна быть освобождена также в dll, а не в python)!