背景:因为使用的是python版本的程序,最终要集成到C++环境的架构中,也就是说架构是c++的,交付用户为c++的接口,但是调用的是python的库,因此需要学习在c++环境下调用python。因为对python不熟悉,可以说有点一抹黑,因此从简到难逐步探索。首先在c++的工程中实现调用单个简单的python脚本(.py脚本文件),然后再调用python编译成的库(.so),最后将复杂的python“工程”编译成库,用c++写好接口被总工程可调用。使用的IDE是codeblocks。

翻看了很多博客,都是假设我们已经知道了设置操作,对我这种操作小白来说及其痛苦。翻墙的油管视频实在是太慢,国内的优酷实在没什么干货。。。遂这样拆解任务由简到难,也可以为不同的需求提供不同的方式。

1. C++调用python脚本文件

(1)配置环境

用codeblocks(后面简称CB)建立一个console的c++工程,取名叫consoleUseSo,里面只有一个main.cpp文件。

配置好Python的环境,具体配置方法请见:

https://blog.csdn/u014794992/article/details/52901147

总结起来基本就是:右键工程,点击build options,左栏选择最上面的工程名(不要选Debug或者Release否则得设置两遍)。我默认的编译器是GNU GCC Compiler,这个不用去管。需要设置的是:点击linker settings选项卡,在Link Libraries里面选择python的库文件路径,一般是usr/lib/python2.7/config-x86_64-linux-gnu/libpython2.7.so,CB可以选择设为相对路径。完成后点击search directories选项卡,在下面的complier选项卡下,选择Python的include路径,一般为:usr/include/python2.7;然后在Linker选项卡中,选择lib路径,一般为:usr/lib/python2.7。这样编译环境就设置好了。在main.cpp中,需要加上:

#include <python2.7/Python.h>
或者
#include <Python.h>

(2)简单的python脚本文件

写一个简单的Python脚本文件,叫做:your_file.py

#-* -coding: UTF-8 -* -

def display(name):
    print "hi",name  

class test:
    def say(self):
        print 'hello'

里面有简单的屏幕打印函数。

将此your_file.py文件放置在consoUseSo工程的目录里面,即和main.cpp在同一路径下!后面会说怎么设置路径

(3)main.cpp中调用python脚本

在main.cpp中添加如下代码:

#include <iostream>
#include "stdio.h"
#include <python2.7/Python.h> //只写<Python.h>也可以

int main()
{
    // 初始化Python
    //在使用Python系统前,必须使用Py_Initialize对其
    //进行初始化。它会载入Python的内建模块并添加系统路
    //径到模块搜索路径中。这个函数没有返回值,检查系统
    //是否初始化成功需要使用Py_IsInitialized。
    Py_Initialize();

    // 检查初始化是否成功
    if ( !Py_IsInitialized() ) {
        return -1;
    }

    // 添加当前路径。这里注意下面三句都不可少,
    //添加的是当前路径。但是我打印了sys.path,
    //出来了好多路径,有点类似环境变量路径的东西。这点不太懂怎么就成当前路径了
    PyRun_SimpleString("import sys");
    PyRun_SimpleString("print '---import sys---'");
    //下面这个./表示当前工程的路径,如果使用../则为上级路径,根据此来设置
    PyRun_SimpleString("print sys.path.append('./')"); 

    PyObject *pName,*pModule,*pDict,*pFunc,*pArgs;

    // 载入名为your_file的脚本
    pName = PyString_FromString("your_file");
    pModule = PyImport_Import(pName);
    if ( !pModule ) {
        printf("can't find your_file.py");
        getchar();
        return -1;
    }

    pDict = PyModule_GetDict(pModule);
    if ( !pDict ) {
        return -1;
    }
    printf("----------------------\n");

    // 找出函数名为display的函数
    pFunc = PyDict_GetItemString(pDict, "display");
    if ( !pFunc || !PyCallable_Check(pFunc) ) {
        printf("can't find function [display]");
        getchar();
        return -1;
     }

    //将参数传进去。1代表一个参数。
    pArgs = PyTuple_New(1);

    //  PyObject* Py_BuildValue(char *format, ...)
    //  把C++的变量转换成一个Python对象。当需要从
    //  C++传递变量到Python时,就会使用这个函数。此函数
    //  有点类似C的printf,但格式不同。常用的格式有
    //  s 表示字符串,
    //  i 表示整型变量,
    //  f 表示浮点数,
    //  O 表示一个Python对象。
    //这里我要传的是字符串所以用s,注意字符串需要双引号!
    PyTuple_SetItem(pArgs, 0, Py_BuildValue("s"," python in C++"));
    // 调用Python函数
    PyObject_CallObject(pFunc, pArgs);

    // 关闭Python
    Py_Finalize();
    return 0;
}

具体函数含义解释还可见此,我也主要参考的这个博客:https://wwwblogs/apexchu/p/5015961.html

点击build,然后运行,如下:

不知道为什么打印出来的sys.path.append就成了None,我只打印sys.path出来了好多类似环境变量的路径。此处不懂,可能是加了个”./"就表示当前工程路径了?路径可以设置为相对路径,也可以设置为绝对路径。比如我要是把python放在工程上一级目录,路径可以设置为:

PyRun_SimpleString("print sys.path.append('../')");

也可以设置为绝对路径,比如:

PyRun_SimpleString("print sys.path.append('/home/y/Documents/aaa/code/sotest/')");

(4)调用包含main函数的脚本

题外话,供自己备忘...

这个PyRun_SimpleString()函数就是非常强大的调用脚本的函数。上面说的是指向了一个路径,然后后面用PyString_FromString函数来寻找这个路径下面的your_file.py文件,然后进一步用PyDict_GetItemString函数来搜索我们想要的函数。

如果是一个本身包含main函数的python脚本,可以用PyRun_SimpleString函数直接运行(用execfile)。假定我们的demo-video.py就是这样的一个脚本,我们可以这样调用:

PyRun_SimpleString("import sys");
PyRun_SimpleString("print sys.path.append('/home/y/py/tools/')");
PyRun_SimpleString("execfile('/home/y/py/tools/demo-video.py')");

前提当然是和(1)中一样设置好路径和头文件,build之后运行,可以自动运行脚本中的main函数内容。

官方文档在这里,然而没有例子,没有什么卵用:https://docs.python/3/c-api/veryhigh.html#c.PyCompilerFlags

2. C++工程调用python生成的so库

有了上面的铺垫,此处就简单了。其实用的是一个函数PyRun_SimpleString,只要在这个函数中,设置好了so库的路径,就ok了。但是为了防止脑残的我后面还会忘记,还是稍微的详细说下。

以第1节中的工程为例,因为我们建立了your_file.py这个python脚本,然后我们利用已经内置在python里的cython来将我们这个脚本打包成库。方法是:建立另一个脚本文件setup.py,跟your_file.py放在一个路径下,setup.py里面写:

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules = cythonize(["your_file.py"]))

这里就是用cython来将Python脚本打包成动态链接库.so,这里的例子比较简单,应该是导出了your_file.py中的所有函数。

接着说怎么打包,写好了这个setup.py后,在这两个文件路径下,打开终端,输入:

$ python setup.py build_ext

就可以发现在当前文件夹里多了一个“build”文件夹,下面的lib.linux-x86_64-2.7文件夹里面的your_file.so就是我们编译成功的动态链接库了。

在第1节中的工程中调用它的方法就是,将路径设置好,就这么easy,其它的用法都一样。

PyRun_SimpleString("print sys.path.append('/home/y/Documents/aaa/code/sotest/build/lib.linux-x86_64-2.7/')");

在build options里竟然都不用设置....因为这里代码意思就是直接在路径下找到这个文件里的某个函数进行调用。

 

更多推荐

c++ 调用Python脚本或者动态库——环境Ubuntu 16.04下用codeblocks