Boost.Python pozwala na wykorzystanie klas i metod zaimplementowanych w C++ w Pythonie i odwrotnie. Skupiłem się na pierwszej części. Garść przydatnych linków:
Takie mieszanie technologi, niestety sprawia, że szukanie błędów staje się trochę denerwujące, ale już po dwóch godzinach udało mi się uzyskać działający przykład, który mniej więcej jest tym co chciałem uzyskać. Co ciekawe (jak mówi dokumentacji), kod napisany w C++, rzadko będzie wymagał zmian dostosowujących go do współpracy z Boost.Python-em, co uważam jest dużym plusem.
W pierwszej kolejności należy stworzyć plik źródłowy (tutaj
SimplePrinter.cpp), z deklaracją wrappera - gdzie mówimy jakie interfejsy będą widoczny w Pythonie. Boost.Python posiada abstrakcję na
std::vector, z której tutaj skorzystałem. Później korzystając z takiego modułu, należy stworzyć specjalny obiekt, dzięki któremu można skonwertować np. listę pythonową i skorzystać z danych w niej zawartych w kodzie C++ (przykład na końcu). Kilka ważnych uwag.
- Należy pamiętać aby nazwa modułu (w BOOST_PYTHON_MODULE) była identyczna z nazwą pliku biblioteki dynamicznej, inaczej podczas importowanie takiego modułu można natknąć się niewiele mówiący błąd.
- Nazywanie metody "print" sprawiła, że uzyskiwałem błąd, gdy próbowałem z takiej metody skorzystać. Najpewniej jest to związane z jakimś konfliktem, gdyż print (Python 2) jest słowem kluczowym.
#include <iostream>
#include <string>
#include <vector>
struct SimplePrinter
{
void setWords(std::vector<std::string> words) {
_words = words;
}
size_t count() const {
return _words.size();
}
void show() {
std::cout << "All: ";
for (const auto& w : _words) {
std::cout << w << " ";
}
std::cout << std::endl;
}
private:
std::vector<std::string> _words;
};
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
using namespace boost::python;
BOOST_PYTHON_MODULE(simple_printer)
{
class_<std::vector<std::string>>("VectorOfStrings")
.def(vector_indexing_suite<std::vector<std::string>>());
class_<SimplePrinter>("SimplePrinter")
.def("setWords", &SimplePrinter::setWords)
.def("count", &SimplePrinter::count)
.def("show", &SimplePrinter::show);
}
W celu skompilowania przykładu skorzystałem z Pythonowego
distutils, które umożliwia budowanie modułów (nie miałem z tym narzędziem wcześniej do czynienia, ale wydaje się bardzo schludnym rozwiązaniem). W pierwszej kolejności należy stworzyć plik
setup.py (w moim przypadku w tym samym katalogu, gdzie znajdowały się źródła). Tutaj ponownie, trzeba sprawdzić, czy nazwa modułu zgadza się z tą, która została zadeklarowana w wraperze C++. Co ważne, moja klasa korzysta z nowego standardu C++14, stąd dodatkowa flaga:
extra_compile_args.
#!/usr/bin/env python
from distutils.core import setup
from distutils.extension import Extension
setup(name="PackageName",
ext_modules=[
Extension(name="simple_printer",
sources=["SimplePrinter.cpp"],
libraries=["boost_python"],
extra_compile_args=["-std=c++14"])
])
Budowanie:
$ cd cpp_boost_python
$ ls
setup.py SimplePrinter.cpp
$ python setup.py build
running build
running build_ext
building 'simple_printer' extension
creating build
creating build/temp.linux-x86_64-2.7
x86_64-linux-gnu-gcc -pthread -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fno-strict-aliasing -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -fPIC -I/usr/include/python2.7 -c SimplePrinter.cpp -o build/temp.linux-x86_64-2.7/SimplePrinter.o -std=c++14
cc1plus: warning: command line option ‘-Wstrict-prototypes’ is valid for C/ObjC but not for C++
creating build/lib.linux-x86_64-2.7
c++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -Wl,-z,relro -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wl,-Bsymbolic-functions -Wl,-z,relro -D_FORTIFY_SOURCE=2 -g -fstack-protector-strong -Wformat -Werror=format-security build/temp.linux-x86_64-2.7/SimplePrinter.o -lboost_python -o build/lib.linux-x86_64-2.7/simple_printer.so
Po zbudowaniu biblioteka będzie znajdować się w podkatalogu build (build/lib.linux-x86_64-2.7/simple_printer.so).
Na sam koniec test działania.
$ cd build/lib.linux-x86_64-2.7
$ ls
simple_printer.so
$ python
>>> import simple_printer
>>> s = simple_printer.SimplePrinter()
>>> words = simple_printer.VectorOfStrings()
>>> words.extend(w for w in ["first", "second"])
>>> s.setWords(words)
>>> s.count()
2
>>> s.show()
All: first second