python是一种非常强大的胶水语言,可以灵活的嵌入到c++和java等主流语言中。python提供了一套C的API库,使得开发者能够很方便的从C、C++的程序中调用python中的各个功能模块。
c++ 调用 python ,本质上是在 c++ 中启动了一个 python 解释器,由解释器对 python 相关的代码进行执行,执行完毕后释放资源,达到调用目的。
从操作步骤上看,C++调用Python低层接口可以分为几个阶段:
说白了,即写一个C文件,执行【Python解释器初始化、导入模块,导入函数,构造输入参数,调用函数,解析返回值,终止Python解释器】。
官方文档python和C相互调用
和环境相关的接口如下:
void Py_Initialize():
初始化python解释器.C/C++中调用Python之前必须先初始化解释器
int Py_IsInitialized():
返回python解析器的是否已经初始化完成,如果已完成,返回大于0,否则返回0
void Py_Finalize() :
撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化,
并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。
int PyRun_SimpleString(const char*) :
执行一个简单的执行python脚本命令的函数
int PyRun_SimpleFile(FILE *fp, const char *filename):
从fp中把python脚本的内容读取到内容中并执行,filename应该为fp对应的文件名
第一种方法示例:
#include "Python.h"
int main()
{
Py_Initialize(); // 初始化
PyRun_SimpleString("print('hello')");
Py_Finalize(); //释放资源
}
第一种方法存在的问题:
python api提供了动态加载模块并且执行函数的能力,具体会涉及到下面几个api。
//加载模块
PyObject* PyImport_ImportModule(char *name)
PyObject* PyImport_Import(PyObject *name)
PyObject* PyString_FromString(const char*)
上面两个api都是用来动态加载python模块的。区别在于前者一个使用的是C的字符串,而后者的name是一个python对象,
这个python对象需要通过PyString_FromString(const char*)来生成,其值为要导入的模块名
//导入函数相关
PyObject* PyModule_GetDict( PyObject *module)
PyModule_GetDict()函数可以获得Python模块中的函数列表。PyModule_GetDict()函数返回一个字典。字典中的关键字为函数名,值为函数的调用地址。
字典里面的值可以通过PyDict_GetItemString()函数来获取,其中p是PyModule_GetDict()的字典,而key则是对应的函数名
PyObject* PyObject_GetAttrString(PyObject *o, char *attr_name)
PyObject_GetAttrString()返回模块对象中的attr_name属性或函数,相当于Python中表达式语句:o.attr_name
//调用函数相关
PyObject* PyObject_CallObject( PyObject *callable_object, PyObject *args)
PyObject* PyObject_CallFunction( PyObject *callable_object, char *format, ...)
使用上面两个函数可以在C程序中调用Python中的函数。callable_object为要调用的函数对象,也就是通过上述导入函数得到的函数对象,
而区别在于前者使用python的tuple来传参,后者则使用类似c语言printf的风格进行传参。
如果不需要参数,那么args可能为NULL。返回成功时调用的结果,或失败时返回NULL。
这相当于Python表达式 apply(callable_object, args) 或 callable_object(*args)
python脚本
#cat script/sayHello.py
def say():
print("hello")
C++代码
#include //C++和python混合编程的头文件
#include
using namespace std;
int main() {
//初始化python解释器
Py_Initialize();
if (!Py_IsInitialized()) {
cout << "python init fail" << endl;
return 0;
}
//初始化python系统文件路径,保证可以访问到 .py文件
//PyRun_SimpleString:把python代码当作一个字符串传给解释器来执行。
PyRun_SimpleString("import sys");
/*把python脚本文件放入当前目录下的script文件夹下
sys.path是一个列表 list, 它里面包含了已经添加到系统的环境变量路径。
当我们要添加自己的引用模块搜索目录时,可以通过列表 list 的 append()方法;*/
PyRun_SimpleString("sys.path.append('./script')");
//PyImport_ImportModule:动态加载python模块,相当于导入python脚本文件
//调用python文件名。当前的测试python文件名是test.py。在使用这个函数的时候,只需要写文件的名称就可以了。不用写后缀。
PyObject* pModule = PyImport_ImportModule("sayHello");
if (pModule == NULL) {
cout << "module not found" << endl;
return 1;
}
/*PyObject* PyObject_GetAttrString(PyObject *o, char *attr_name)
PyObject_GetAttrString()返回模块对象中的attr_name属性或函数,
相当于Python中表达式语句:o.attr_name
相当于找到导入的python脚本文件里边的某个函数*/
PyObject* pFunc = PyObject_GetAttrString(pModule, "say");
if (!pFunc || !PyCallable_Check(pFunc)) {
cout << "not found function add_num" << endl;
return 0;
}
/*PyObject_CallObject:在C程序中调用python函数
参数1:通过导入函数获得的函数对象
参数2:被调用函数所需的参数*/
PyObject_CallObject(pFunc, NULL);
/* 撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化,
并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。*/
Py_Finalize();
return 0;
}
注意
要在release下进行否则会报错。
C++调用python程序时,出现如下问题:
无法解析的外部符号 __imp___Py_RefTotal
无法解析的外部符号 __imp___Py_NegativeRefcount
参数
在C/C++中,所有的Python类型都被声明为PyObject型,为了能够让C++能够操作python的数据,python提供了python各种数据类型和C语言数据类型的转换操作,具体的使用方法见参考链接。
在Python/C API中提供了Py_BuildValue()函数对数字和字符串进行转换处理,使之变成Python中相应的数据类型。其函数原型如下所示:
PyObject* Py_BuildValue( const char *format, ...)
Py_BuildValue()提供了类似c语言printf的参数构造方法,format是要构造的参数的类型列表,函数中剩余的参数即要转换的C语言中的整型、浮点型或者字符串等。
其返回值为PyObject型的指针。
返回值
python函数的返回值也是PyObject类型,因此,在python脚本返回到C/C++之后,需要解构Python数据为C的类型,这样C/C++程序中才可以使用Python里的数据。但是,由于python的返回值有多种数据结构类型,因此,我们需要为每个类型进行转换。
总体思路都是根据类型逐个从值从PyObject中提取。python提供了下面函数来完成这个功能
int PyArg_Parse( PyObject *args, char *format, ...)
根据format把args的值转换成c类型的值,format接受的类型和上述Py_BuildValue()的是一样的
释放资源
Python使用引用计数机制对内存进行管理,实现自动垃圾回收。在C/C++中使用Python对象时,应正确地处理引用计数,否则容易导致内存泄漏。在Python/C API中提供了Py_CLEAR()、Py_DECREF()等宏来对引用计数进行操作。
每个PyObject对象都有一个引用计数,用于垃圾回收,如果不能在恰当的时候增加(Py_INCREF)或减少(Py_DECREF)引用计数,则会发生:
当使用Python/C API中的函数创建列表、元组、字典等后,就在内存中生成了这些对象的引用计数。在对其完成操作后应该使用Py_CLEAR()、Py_DECREF()等宏来销毁这些对象。其原型分别如下所示
void Py_CLEAR(PyObject *o)
void Py_DECREF(PyObject *o)
其中,o的含义是要进行操作的对象。
对于Py_CLEAR()其参数可以为NULL指针,此时,Py_CLEAR()不进行任何操作。而对于Py_DECREF()其参数不能为NULL指针,否则将导致错误。
#include //C++和python混合编程的头文件
#include
using namespace std;
int main() {
//初始化python解释器
Py_Initialize();
if (!Py_IsInitialized()) {
cout << "python init fail" << endl;
return 0;
}
//PyRun_SimpleString:把python代码当作一个字符串传给解释器来执行。
PyRun_SimpleString("import sys");
/*把python脚本文件放入当前目录下的script文件夹下
sys.path是一个列表 list, 它里面包含了已经添加到系统的环境变量路径。
当我们要添加自己的引用模块搜索目录时,可以通过列表 list 的 append()方法;*/
PyRun_SimpleString("sys.path.append('./script')");
//PyImport_ImportModule:动态加载python模块,相当于导入python脚本文件
PyObject* pModule = PyImport_ImportModule("sayHello");
if (pModule == NULL) {
cout << "module not found" << endl;
return 1;
}
/*PyObject* PyObject_GetAttrString(PyObject *o, char *attr_name)
PyObject_GetAttrString()返回模块对象中的attr_name属性或函数,
相当于Python中表达式语句:o.attr_name
相当于找到导入的python脚本文件里边的某个函数*/
PyObject* pFunc = PyObject_GetAttrString(pModule, "add_num");
if (!pFunc || !PyCallable_Check(pFunc)) {
cout << "not found function add_num" << endl;
return 0;
}
/*将参数转换为PyObject类型*/
PyObject* args = Py_BuildValue("(ii)", 5, 2);
/*PyObject_CallObject:在C程序中调用python函数
参数1:通过导入函数获得的函数对象
参数2:被调用函数所需的参数*/
PyObject* pRet = PyObject_CallObject(pFunc, args);
//释放参数内存
Py_DECREF(args);
int res = 0;
//把参数返回值转换为C类型
PyArg_Parse(pRet, "i", &res);
//释放返回值内存
Py_DECREF(pRet);
cout << res << endl;
Py_DECREF(pModule);
Py_DECREF(pFunc);
/* 撤销Py_Initialize()和随后使用Python/C API函数进行的所有初始化,
并销毁自上次调用Py_Initialize()以来创建并为被销毁的所有子解释器。*/
Py_Finalize();
return 0;
}
前言
首先要查看python的版本是release版本还是debug版本,一般安装的python都是Release版本。VS编写C++时,改为Release模式,这要与python的版本一致,否则会报错:
无法解析的外部符号 __imp___Py_RefTotal
如果将版本调整为相同的Release,则不会存在此问题。
后续见参考链接,感觉不是很全面。
第一种方法:图片在python中显示,无返回值。
python代码
# opencv显示一张图片
import cv2
def show_image(img_path):
img = cv2.imread(img_path)
cv2.imshow("img-show", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == '__main__':
show_image('E:/green_screen_keying/test_set/test1.png')
C++代码
#include
#include
using namespace std;
int main() {
Py_Initialize();
if (!Py_IsInitialized()) {
cout << "python initialize failed!" << endl;
return 0;
}
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./script')");
PyObject* pModule = PyImport_ImportModule("show_img");
if (pModule == NULL) {
cout << "module not found!" << endl;
return 0;
}
PyObject* pFunc = PyObject_GetAttrString(pModule, "show_image");
if (pFunc == NULL || PyCallable_Check(pFunc) == NULL) {
cout << "function not found!" << endl;
return 0;
}
PyObject* args = Py_BuildValue("(s)", "E:/green_screen_keying/test_set/test1.png");
PyObject_CallObject(pFunc, args);
Py_DECREF(pModule);
Py_DECREF(pFunc);
Py_DECREF(args);
Py_Finalize();
return 0;
}
C++使用numpy返回值的前提
环境:
C++使用opencv显示图片的前提
python代码
# opencv显示一张图片
import cv2
def show_image(img_path):
img = cv2.imread(img_path)
cv2.imshow("img-show", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
def show_image_return_numpy(img_path):
img = cv2.imread(img_path)
img = cv2.resize(img, (400, 400)) # numpy类型
return img
if __name__ == '__main__':
show_image('E:/green_screen_keying/test_set/test1.png')
C++代码
#include
#include
#include //numpy的头文件
#include //opencv的头文件
using namespace cv;
using namespace std;
int main() {
Py_Initialize();
if (!Py_IsInitialized()) {
cout << "python initialize failed!" << endl;
return 0;
}
import_array();//加载numpy相关的库
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./script')");
PyObject* pModule = PyImport_ImportModule("show_img");
if (pModule == NULL) {
cout << "module not found!" << endl;
return 0;
}
PyObject* pFunc = PyObject_GetAttrString(pModule, "show_image_return_numpy");
if (pFunc == NULL || PyCallable_Check(pFunc) == NULL) {
cout << "function not found!" << endl;
return 0;
}
PyObject* args = Py_BuildValue("(s)", "E:/green_screen_keying/test_set/test1.png");
//PyObject_CallObject(pFunc, args);
PyObject* pRetValue = PyObject_CallObject(pFunc, args);
/* 解析返回结果 */
PyArrayObject* ret_array;
PyArray_OutputConverter(pRetValue, &ret_array);
//npy_intp代表数组的维度,指向数组的尺寸/形状的指针。
npy_intp* shape = PyArray_SHAPE(ret_array);
Mat imgReturn(shape[0], shape[1], CV_8UC3, PyArray_DATA(ret_array));
cv::imshow("res", imgReturn);
cv::waitKey(0);
Py_DECREF(pModule);
Py_DECREF(pFunc);
Py_DECREF(args);
Py_DECREF(pRetValue);
Py_DECREF(ret_array);
Py_Finalize();
return 0;
}
继续更新中…
C++调用python脚本
C++调用numpy和OpenCV
C++调用python时opencv和numpy的转换和使用