在 C++ 中嵌入 Python 并使用 Boost.Python 从 C++ 代码调用方法
Embedding Python in C++ and calling methods from the C++ code with Boost.Python
我尝试将 Python 脚本嵌入到我的 C++ 程序中。在阅读了一些关于嵌入和扩展的内容后,我了解了如何打开我自己的 python 脚本以及如何将一些整数传递给它。但是现在我不知道如何解决我的问题。我必须同时执行这两项操作,从 C++ 调用 Python 函数并从我的嵌入式 Python 脚本调用 C++ 函数。但我不知道我必须从哪里开始。我知道我必须编译一个 .so 文件来将我的 C++ 函数公开给 Python,但我无能为力,因为我必须嵌入我的 Python 文件并使用 C++ 代码控制它(我必须用脚本语言扩展一个大型软件,使一些逻辑易于编辑)。
那么,有什么办法可以做到这两点吗?从 C++ 调用 Python 函数和从 Python?
调用 C++ 函数
这是我的 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
简而言之,与嵌入式 Python 静态链接的 Python 扩展需要在初始化解释器之前将其模块初始化函数显式添加到初始化 table。
PyImport_AppendInittab("hello", &inithello);
Py_Initialize();
Boost.Python 使用 BOOST_PYTHON_MODULE
宏来定义 Python 模块初始值设定项。结果函数不是模块导入器。这种差异类似于创建 example.py
模块并调用 import example
.
导入模块时,Python会先检查模块是否为内置模块。如果模块不存在,那么 Python 将搜索 module search path 并尝试根据模块名称查找 python 文件或库。如果找到一个库,那么 Python 期望该库提供一个函数来初始化模块。一旦找到,导入将在模块 table 中创建一个空模块,然后对其进行初始化。对于静态链接的模块,例如原始代码中的hello
,模块搜索路径将无济于事,因为没有库可供其查找。
对于嵌入,module table and initialization function documentation states that for static modules, the module initializer function will not be automatically called unless there is an entry in the initialization table. For Python 2 and Python 3, one can accomplish this by calling PyImport_AppendInittab()
before Py_Initialize()
:
BOOST_PYTHON_MODULE(hello)
{
// ...
}
PyImport_AppendInittab("hello", &inithello);
Py_Initialize();
// ...
boost::python::object hello = boost::python::import("hello");
另请注意,用于嵌入的 Python 的 C API 更改了 Python 2 和 3 之间模块初始化函数的命名约定,因此对于 BOOST_PYTHON_MODULE(hello)
,一个可能需要对 Python 2 使用 &inithello
,对 Python 3 使用 &PyInit_hello
。
这是一个完整的示例 demonstrating,其中嵌入了 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()
. Per the Embedding - Getting started 部分:
Note that at this time you must not call Py_Finalize()
to stop the interpreter. This may be fixed in a future version of boost.python.
我尝试将 Python 脚本嵌入到我的 C++ 程序中。在阅读了一些关于嵌入和扩展的内容后,我了解了如何打开我自己的 python 脚本以及如何将一些整数传递给它。但是现在我不知道如何解决我的问题。我必须同时执行这两项操作,从 C++ 调用 Python 函数并从我的嵌入式 Python 脚本调用 C++ 函数。但我不知道我必须从哪里开始。我知道我必须编译一个 .so 文件来将我的 C++ 函数公开给 Python,但我无能为力,因为我必须嵌入我的 Python 文件并使用 C++ 代码控制它(我必须用脚本语言扩展一个大型软件,使一些逻辑易于编辑)。
那么,有什么办法可以做到这两点吗?从 C++ 调用 Python 函数和从 Python?
调用 C++ 函数这是我的 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
简而言之,与嵌入式 Python 静态链接的 Python 扩展需要在初始化解释器之前将其模块初始化函数显式添加到初始化 table。
PyImport_AppendInittab("hello", &inithello);
Py_Initialize();
Boost.Python 使用 BOOST_PYTHON_MODULE
宏来定义 Python 模块初始值设定项。结果函数不是模块导入器。这种差异类似于创建 example.py
模块并调用 import example
.
导入模块时,Python会先检查模块是否为内置模块。如果模块不存在,那么 Python 将搜索 module search path 并尝试根据模块名称查找 python 文件或库。如果找到一个库,那么 Python 期望该库提供一个函数来初始化模块。一旦找到,导入将在模块 table 中创建一个空模块,然后对其进行初始化。对于静态链接的模块,例如原始代码中的hello
,模块搜索路径将无济于事,因为没有库可供其查找。
对于嵌入,module table and initialization function documentation states that for static modules, the module initializer function will not be automatically called unless there is an entry in the initialization table. For Python 2 and Python 3, one can accomplish this by calling PyImport_AppendInittab()
before Py_Initialize()
:
BOOST_PYTHON_MODULE(hello)
{
// ...
}
PyImport_AppendInittab("hello", &inithello);
Py_Initialize();
// ...
boost::python::object hello = boost::python::import("hello");
另请注意,用于嵌入的 Python 的 C API 更改了 Python 2 和 3 之间模块初始化函数的命名约定,因此对于 BOOST_PYTHON_MODULE(hello)
,一个可能需要对 Python 2 使用 &inithello
,对 Python 3 使用 &PyInit_hello
。
这是一个完整的示例 demonstrating,其中嵌入了 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()
. Per the Embedding - Getting started 部分:
Note that at this time you must not call
Py_Finalize()
to stop the interpreter. This may be fixed in a future version of boost.python.