Python和C++之间的通信

3
我希望创建一个Python模块,可以从C++类中调用其函数,并从该类调用C++函数。
我已经查看了Boost,但似乎没有什么意义。它引用了一个共享库(我不知道如何创建),我无法跟随他们在示例中使用的代码(它看起来非常混乱)。
这里是他们的“Hello World”教程(http://www.boost.org/doc/libs/1_55_0b1/libs/python/doc/tutorial/doc/html/index.html#python.quickstart)。
按照C/C++传统,让我们从“Hello, World”开始。一个C++函数:
char const* greet()
{
   return "hello, world";
}

可以通过编写Boost.Python包装器来将其暴露给Python:
include <boost/python.hpp>

BOOST_PYTHON_MODULE(hello_ext)
{
    using namespace boost::python;
    def("greet", greet);
}

好的,我们完成了。现在我们可以将其构建为共享库。生成的DLL现在对Python可见。以下是一个示例Python会话:

>>> import hello_ext
>>> print hello_ext.greet()
hello, world

下一步...从头开始构建你的Hello World模块,可以有人帮忙解释正在做什么,最重要的是Python如何知道C++文件。

看看SWIG。Boost.Python需要将其自己的库与您的扩展链接,而且对于使用您的代码的任何人来说,这个库都很难获取。SWIG会为您提供一个.cpp文件,您可以像编译任何其他用C++编写的Python模块一样编译它。 - Adam
2个回答

4
Python不知道C++文件,它只会意识到从C++文件编译出的扩展模块。这个扩展模块是一个对象文件,称为共享库。该文件具有一个接口,对Python来说就像一个正常的Python模块。
在告诉编译器编译C++文件并链接所有必需的库之后,才会存在这个对象文件。当然,第一个需要的库是Boost.Python本身,在编译时必须在系统上可用。
你可以告诉Python为你编译C++文件,这样你就不需要操心编译器和其库标志。为此,你需要一个名为setup.py的文件,在其中使用Setuptools库或标准Distutils定义其他Python模块如何安装到系统上。安装的步骤之一是编译所有扩展模块,称为build_ext阶段。
假设你有以下目录和文件:
hello-world/
├── hello_ext.cpp
└── setup.py

setup.py 的内容如下:

from distutils.core import setup
from distutils.extension import Extension


hello_ext = Extension(
    'hello_ext',
    sources=['hello_ext.cpp'],
    include_dirs=['/opt/local/include'],
    libraries=['boost_python-mt'],
    library_dirs=['/opt/local/lib'])


setup(
    name='hello-world',
    version='0.1',
    ext_modules=[hello_ext])

如您所见,我们正在告诉Python我们想要编译的扩展名,源文件在哪里,以及可以找到包含的库。这是系统相关的。这里展示的示例适用于Mac OS X系统,在该系统中,Boost库是通过MacPorts安装的。
hello_ext.cpp的内容如教程所示,但请注意重新排列内容,使BOOST_PYTHON_MODULE宏出现在必须导出到Python的定义之后。
#include <boost/python.hpp>

char const* greet()
{
   return "hello, world";
}

BOOST_PYTHON_MODULE(hello_ext)
{
    using namespace boost::python;
    def("greet", greet);
}

您可以在命令行上执行以下命令,让Python为您进行编译和链接:

$ python setup.py build_ext --inplace
running build_ext
building 'hello_ext' extension
/usr/bin/clang -fno-strict-aliasing -fno-common -dynamic -pipe -Os -fwrapv -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I/opt/local/include -I/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 -c hello_ext.cpp -o build/temp.macosx-10.9-x86_64-2.7/hello_ext.o
/usr/bin/clang++ -bundle -undefined dynamic_lookup -L/opt/local/lib -Wl,-headerpad_max_install_names -L/opt/local/lib/db46 build/temp.macosx-10.9-x86_64-2.7/hello_ext.o -L/opt/local/lib -lboost_python-mt -o ./hello_ext.so

--inplace 标志告诉 Python 将编译生成的文件放在源文件旁边, 默认处理方式是将它们移动到 build 目录下,以保持源目录的干净整洁。)
完成后,您会在 hello-world 目录下找到一个名为 hello_ext.dll 的新文件(在 Unix 上则是 hello_ext.so)。如果您在该目录中启动 Python 解释器,则可以导入模块 hello_ext 并使用函数 greet,就像 Boost 教程中展示的那样。

3
Python是一种解释性语言。这意味着它需要一个虚拟机来执行语句。例如,如果遇到 a = 5,Python(或者更确切地说是解释你的Python代码的虚拟机)将在内存中创建一个对象,保存一些信息和值5,并确保任何后续引用a都能找到该对象。对于像input这样的更复杂的语句,虚拟机将触发一个硬编码的例程,在返回读取下一段Python代码之前会在幕后进行大量工作。
关于模块。当发出import语句时,Python将在其路径中查找指定的模块名称。这通常是一个只包含纯Python代码的.py文件。但也可以是一个包含已编译例程的.pyd文件,Python可以像可执行文件一样使用它们,就像共享库一样。这个文件包含符号和入口点,因此当解释器找到特殊的方法名mymodule.mymethod()时,它知道在哪里找到要执行的例程并运行它。
但是,这些例程必须符合特定的接口,这就是为什么将C/C++函数暴露给Python并不简单的原因。最明显的问题是Python中的int不是C中的int,也不是short,甚至不是long。它是一个特殊的结构,保存了更多的信息,例如变量被引用的次数(以便能够释放不再引用的变量的内存),它所持有的值的类型等等。当然,典型的C/C++库不能使用这些复杂的类型,而是使用普通的intfloatchar*和其他好看的平凡类型。因此,必须将必要的Python值转换为简单的C类型,以便库可以理解,并将库可能提供的结果转换回可由Python虚拟机使用的格式。这就是所谓的wrapper。包装器还必须处理一些有趣的事情,如引用计数、堆上的内存管理、初始化和终止等等。查看一些示例,了解这种代码的外观。这并不是非常复杂,但仍然需要一些工作。
现在你可以想象一下,在调用非常简单的def("greet", greet);时,Python.Boost库(或其他包装工具)在幕后所做的所有艰苦工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接