前言
C++调用python不只有ROS一种方法,其实我更喜欢调用API接口的这种方法。当然,按我的理解这种方法本质上类似于开启了一个python的虚拟机运行环境。虽然我感觉效率比ROS高,但是并没有验证。一家之言,希望大家辩证看待。
一、准备工作
既然已经做到了混合编程了,那我就默认这项工作应该是一个工程。而且我的项目中,python涉及到了深度学习。那我就以此为例,从头到尾记录一下。
1.CmakeList.txt文件的书写
project(hello_cpython)
cmake_minimum_required(VERSION 3.10)
include_directories("include")
add_library(/*你src中的cpp文件添加到library*/)
add_executable(cnn /*你src中的cpp主程序,cnn什么的的名字自己起*/)
/*以下这句是与混合编程最有关的两句之一,1/2*/
include_directories(
/home/xxxx/anaconda3/envs/slam/include/python3.6m
)
//路径注释(/home/xxxx/anaconda3/,xxxx是你电脑主文件夹的名字;/envs/slam/include/python3.6m,slam是我的虚拟环境的名字,最后的3.6,其实与你当时配置虚拟环境设定的python版本有关)
target_link_libraries(cnn /*添加入library的cpp文件*/)
/*以下这句是与混合编程最有关的两句之一,2/2*/
target_link_libraries(cnn
/home/filetransfer/anaconda3/envs/slam/lib/libpython3.6m.so
)
//不再注释路径,与之前的相似,根据自己的实际情况修改
set(CMAKE_CXX_FLAGS "-std=c++11")
2.工程文件的放置
工程文件夹名称:c_python
子文件夹:src include build
要调用的python文件放在build中
二、混合编程方法
1.代码举例说明
以下代码为cpp主程序的逻辑路径,并不能直接运行,请根据自己的实际情况略作修改。
/*1.混合编程所必需的头文件*/
#include <Python.h>
#include <iostream>
using namespace std;
/*2.主函数*/
int main(int argc, char *argv[]) {
int CurrentFrameID,r1;
Py_Initialize();
/*2.1判断初始化是否成功*/
if(!Py_IsInitialized())
{
printf("Python init failed!\n");
return -1;
}
/*2.2将当前路径添加到虚拟运行环境*/
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
PyRun_SimpleString("print(sys.path)");//会输出一大堆路径,只要其中包括./就OK
/*2.3建立模型和函数指针*/
PyObject* pModule = NULL;//模型指针代表.py函数
PyObject* pyFunc_mix = NULL;//函数指针代表.py中的指定函数
/*2.4导入python文件*/
pModule = PyImport_ImportModule("demo");//根据自己的情况修改,我的python文件为demo.py
if (!pModule) {
printf("Can not open python file!\n");
PyErr_Print();//打印出python运行失败的报错原因
PyErr_Clear();//这两句哪里需要哪里搬,否则不会输出python文件的报错原因
return -1;
}
/*2.5调用python文件中的函数*/
pyFunc_mix = PyObject_GetAttrString(pModule, "hello_cpython");
if(pModule && PyCallable_Check(pyFunc_mix))
{
/*2.6添加c++输入python函数*/
PyObject *pyParams = PyTuple_New(2);//2代表一个元组包括两个变量
PyTuple_SetItem(pyParams,0,Py_BuildValue("i",CurrentFrameID));//第一个变量,整形为i,字符串为s,double是d
PyTuple_SetItem(pyParams,1,Py_BuildValue("i",CurrentFrameID+1));//第二个变量
/*2.7运行python文件*/
PyObject *pyValue = PyObject_CallObject(pyFunc_mix,pyParams);//PyObject_CallObject第一个变量是函数,第二个是参数元组
Py_XDECREF(pyParams);
PyArg_ParseTuple(pyValue,"i",&r1);//读取python输出变量(即hello_cpython函数return的结果)
/*2.8输出读取结果*/
if(pyValue)
{
cout<<r1<<endl;
}
}
/*2.5退出虚拟环境*/
Py_XDECREF(pyValue);
Py_XDECREF(pyFunc_mix);
Py_DECREF(pModule);
Py_Finalize();
return 0;
}
至于python文件完全不需要修改,只需要保证python与c++传参类型和数量一致就好了。
cmake ..
make
得到了一个名为cnn的可执行文件
./cnn
2.传递string的问题
当我传递int和double类型的数据时,都没有出现问题,但是一旦以这种方式传递string类型的数据,就无法传递成功
string C_ID="hello";
PyTuple_SetItem(pyParams,0,Py_BuildValue("s",C_ID));
但是以这种形式就可以顺利传输。
PyTuple_SetItem(pyParams,0,Py_BuildValue("s","hello"));
但是这样就丧失了灵活性,反正很神奇,如果有同学知道如何解决请不吝赐教。
3.循环调用Python文件
如果在实际应用中,需要多次循环调用某一python文件,应该将python虚拟环境的初始化和终结放置在大循环之外。否则将有可能出现段错误。即,如下。
Py_Initialize();
for{
/*这里循环调用python文件*/
}
Py_Finalize();
总结
以上,混合编程很坑,资料很少,祝好运!