Встраивание Python в C++ и вызов методов из кода C++
Я пытаюсь встроить скрипт Python в мою C++ программу. После прочтения некоторых вещей о встраивании и расширении я понимаю, как открыть свой собственный скрипт на python и как передать ему целые числа. Но сейчас я в какой-то момент не понимаю, как решить мою проблему. Я должен сделать как вызов функций Python из C++, так и вызов функций C++ из моего встроенного скрипта Python. Но я не знаю, с чего мне начать. Я знаю, что должен скомпилировать файл.so, чтобы представить свои функции C++ в Python, но я ничего не могу поделать, потому что мне нужно встраивать свой файл Python и управлять им с помощью кода C++ (мне нужно расширить большое программное обеспечение с помощью язык сценариев, чтобы сделать логику легкой для редактирования).
Итак, есть ли способ сделать обе вещи? Вызов функций Python из C++ и вызов функций C++ из Python?
Это мой код C++
#include <Python.h>
#include <boost/python.hpp>
using namespace boost::python;
// <----------I want to use this struct in my python file---------
struct World
{
void set(std::string msg) { this->msg = msg; }
std::string greet() { return msg; }
std::string msg;
};
// Exposing the function like its explained in the boost.python manual
// but this needs to be compiled to a .so to be read from the multiply.py
BOOST_PYTHON_MODULE(hello)
{
class_<World>("World")
.def("greet", &World::greet)
.def("set", &World::set)
;
}
// <---------------------------------------------------------------
int
main(int argc, char *argv[]) // in the main function is only code for embedding the python file, its not relevant to this question
{
setenv("PYTHONPATH",".",1);
PyObject *pName, *pModule, *pDict, *pFunc;
PyObject *pArgs, *pValue;
int i;
if (argc < 3) {
fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
return 1;
}
Py_Initialize();
pName = PyString_FromString(argv[1]);
/* Error checking of pName left out */
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, argv[2]);
/* pFunc is a new reference */
if (pFunc && PyCallable_Check(pFunc)) {
pArgs = PyTuple_New(argc - 3);
for (i = 0; i < argc - 3; ++i) {
pValue = PyInt_FromLong(atoi(argv[i + 3]));
if (!pValue) {
Py_DECREF(pArgs);
Py_DECREF(pModule);
fprintf(stderr, "Cannot convert argument\n");
return 1;
}
/* pValue reference stolen here: */
PyTuple_SetItem(pArgs, i, pValue);
}
pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
printf("Result of call: %ld\n", PyInt_AsLong(pValue));
Py_DECREF(pValue);
}
else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
fprintf(stderr,"Call failed\n");
return 1;
}
}
else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
}
else {
PyErr_Print();
fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
return 1;
}
Py_Finalize();
return 0;
}
и это мой файл Python
import hello_ext #importing the C++ file works only if its compiled as a .so
planet = hello.World() #this class should be exposed to python
planet.set('foo')
def multiply(a,b):
planet.greet()
print "Will compute", a, "times", b
c = 0
for i in range(0, a):
c = c + b
return c
1 ответ
Короче говоря, расширения Python, которые статически связаны со встроенным Python, должны иметь свою функцию инициализации модуля, явно добавленную в таблицу инициализации до инициализации интерпретатора.
PyImport_AppendInittab("hello", &inithello);
Py_Initialize();
Boost.Python использует BOOST_PYTHON_MODULE
макрос для определения инициализатора модуля Python. Полученная функция не является импортером модуля. Эта разница аналогична созданию example.py
модуль и вызов import example
,
При импорте модуля Python сначала проверяет, является ли модуль встроенным модулем. Если модуля там нет, то Python будет искать путь поиска модуля, пытаясь найти файл или библиотеку python на основе имени модуля. Если библиотека найдена, то Python ожидает, что библиотека предоставит функцию, которая будет инициализировать модуль. Найдя, импорт создаст пустой модуль в таблице модулей, а затем инициализирует его. Для статически связанных модулей, таких как hello
в исходном коде путь поиска модуля не будет полезным, поскольку для него нет библиотеки для поиска.
Для встраивания в таблице модулей и документации по функциям инициализации говорится, что для статических модулей функция инициализатора модулей не будет вызываться автоматически, если в таблице инициализации нет записи. Для Python 2 и Python 3 это можно сделать, вызвав PyImport_AppendInittab()
до Py_Initialize()
:
BOOST_PYTHON_MODULE(hello)
{
// ...
}
PyImport_AppendInittab("hello", &inithello);
Py_Initialize();
// ...
boost::python::object hello = boost::python::import("hello");
Также обратите внимание, что C API Python для встраивания измененных соглашений об именах для функций инициализации модулей между Python 2 и 3, поэтому для BOOST_PYTHON_MODULE(hello)
может понадобиться &inithello
для Python 2 и &PyInit_hello
для Python 3.
Вот полный пример, демонстрирующий наличие встроенного Python для импорта demo
пользовательский модуль, который затем импортирует статически связанный hello
модуль. Он также вызывает функцию в пользовательском модуле demo.multiply
, который затем вызовет метод, предоставляемый через статически связанный модуль.
#include <cstdlib> // setenv, atoi
#include <iostream> // cerr, cout, endl
#include <boost/python.hpp>
struct World
{
void set(std::string msg) { this->msg = msg; }
std::string greet() { return msg; }
std::string msg;
};
/// Staticly linking a Python extension for embedded Python.
BOOST_PYTHON_MODULE(hello)
{
namespace python = boost::python;
python::class_<World>("World")
.def("greet", &World::greet)
.def("set", &World::set)
;
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
std::cerr << "Usage: call pythonfile funcname [args]" << std::endl;
return 1;
}
char* module_name = argv[1];
char* function_name = argv[2];
// Explicitly add initializers for staticly linked modules.
PyImport_AppendInittab("hello", &inithello);
// Initialize Python.
setenv("PYTHONPATH", ".", 1);
Py_Initialize();
namespace python = boost::python;
try
{
// Convert remaining args into a Python list of integers.
python::list args;
for (int i=3; i < argc; ++i)
{
args.append(std::atoi(argv[i]));
}
// Import the user requested module.
// >>> import module
python::object module = python::import(module_name);
// Invoke the user requested function with the provided arguments.
// >>> result = module.fn(*args)
python::object result = module.attr(function_name)(*python::tuple(args));
// Print the result.
std::cout << python::extract<int>(result)() << std::endl;
}
catch (const python::error_already_set&)
{
PyErr_Print();
return 1;
}
// Do not call Py_Finalize() with Boost.Python.
}
Содержание demo.py
:
import hello
planet = hello.World()
planet.set('foo')
def multiply(a,b):
print planet.greet()
print "Will compute", a, "times", b
c = 0
for i in range(0, a):
c = c + b
return c
Использование:
$ ./a.out demo multiply 21 2
foo
Will compute 21 times 2
42
В приведенном выше коде я решил использовать Boost.Python вместо Python/C API, а комментарии C++ помечены эквивалентным кодом Python. Я считаю, что это гораздо более кратким и гораздо менее подвержен ошибкам. Если происходит ошибка Python, Boost.Python сгенерирует исключение, и весь подсчет ссылок будет обработан соответствующим образом.
Кроме того, при использовании Boost.Python не вызывайте Py_Finalize()
, Согласно разделу " Внедрение - Начало работы ":
Обратите внимание, что в это время вы не должны звонить
Py_Finalize()
остановить переводчика. Это может быть исправлено в будущей версии boost.python.