一.前言
最近碰到了一个奇怪的需求,需要用C++调用Python脚本(Python调用C++倒是常见,反过来以前真没见过),因此去网上学习了一波顺便将学习过程中的笔记分享一波,话不多说,请看下文。
二.VS中调用Python配置
首先在Visual Studio中创建项目,注意需要选择X64进行编译,即:
然后,需要将Python安装目录下的include
和libs
目录引入到本项目中去,具体做法如下:
- 对于
include
,选择项目–>配置属性–>C/C+±->附加包目录,在附加目录那一栏填入include
的绝对路径。 - 对于
libs
,选择项目–>配置属性–>链接器–>附加库目录,在附加库目录那一栏填入libs
的绝对路径,注意还需要将pythonxx.lib
复制一份为pythonxx_d.lib
仍然放在该文件中。
xx表示python的版本,比如3.8版本即为python38
完成上述步骤就可以在VS的cpp源文件中引入头文件Python.h
头文件,并使用Python提供的C API库了。
三.C++中Python的使用方法
3.1 Python解释器环境
首先需要为程序提供解释器环境,在程序结束后要销毁创建的解释器,与解释器相关的接口如下:
// 设置python环境EXE文件的路径
void Py_SetPythonHome(python_path);
// 初始化Python解释器
void Py_Initialize();
// 解释器是否初始化完成,失败则返回0
int Py_IsInitialized();
// 销毁创建的解释器
void Py_Finalize();
3.2 调用Python模块和调用相关的函数
这里指的Python模块包括封装好的库以及自定义的Py文件。首先给出最简单的调用方法(无法传递参数),其对应的接口如下所示:
// 直接以字符串的形式传入python代码
int PyRun_SimpleString(const char*);
// PyRun_SimpleString("print('hello world')");
// 将python脚本传入直接执行,fp为脚本对应的文件指针,filename为脚本名
int PyRun_SimpleFile(FILE *fp, const char *filename);
倘若要调用自定义的python脚本,则可以使用下面的常用接口:
// 加载模块,传入python脚本名或封装好的库名
PyObject* PyImport_ImportModule(char *name);
// 获取模块中的函数列表,该函数返回一个key为函数名,value为函数调用地址的字典
PyObject* PyModule_GetDict( PyObject *module);
// 从函数字典中获取相应的函数,dp为函数字典,key为要获取函数的名称
PyDict_GetItemString(PyObject *dp, const char *key);
// 直接传递属性名来获取模块中的相应对象
PyObject* PyObject_GetAttrString(PyObject *o, char *attr_name);
// 函数调用, callable_object为函数对象,args为需要传入的参数
PyObject* PyObject_CallObject( PyObject *callable_object, PyObject *args);
// 参数传递
// 将C++的数据类型转换为python对应的类型
PyObject* Py_BuildValue( const char *format, ...);
// 定义size为len的元组
PyObject* PyTuple_New( Py_ssize_t len);
// 创建完元组后,往元组内添加元素,p为要操作的元组,pos为索引,o为通过Py_BudilValue转换后的值
int PyTuple_SetItem( PyObject *p, Py_ssize_t pos, PyObject *o) ;
注意函数的参数列表必须为元组。
3.3 C++与Py的数据类型转换
上节介绍到通过Py_BuildValue
函数可以进行数据类型的转换,下面给出常用数据类型对应的转换关系表:
format值 | C语言对应数据类型 | Py对应数据类型 |
---|---|---|
s | 使用utf-8 编码将以null结尾的字符串 | String对象 |
b/i/h/l | char/int/short int/long | 整数对象 |
f/d | float/double | 浮点数对象 |
对于C++传递数组到Python限于篇幅,本文就不展开了,具体可以参考下C调用Python(传递数字、字符串、list数组(一维、二维),结构体)
3.4 返回值的处理
python脚本的返回值可以用下面的API进行处理:
int PyArg_Parse( PyObject *args, char *format, ...);
// PyObject* pReturn = PyEval_CallObject(pFunc, pArgs);
// int nResult;
// PyArg_Parse(pReturn, "i", &nResult);
需要注意若返回的是多个参数,可以用处理列表、元组或字典的方法来进行解析。
四.使用示例
首先创建一个名为temp
的py文件,其中包含的源码如下:
def hello(name):
print("hello {}".format(name))
def add(a,b):
return a + b
然后将该文件放至VS项目可执行文件同目录下,具体做法为:
- 若使用Debug(x64)模式,则在项目目录–>x64–>Debug目录下
- 若使用Release(X64)模式,则在项目目录–>x64–>Release目录下
或者也可以利用PyRun_SimpleString
函数来调用sys
模块来进行指定如:
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('C:/xxxxx')"); // c:/xxxxx表示py文件所在的位置
然后,便是c++程序的编写,对应的源码如下:
#include <iostream>
#include<Python.h>
using namespace std;
int main() {
// 设置python的执行路径
Py_SetPythonHome(L"C:/xxxxx");
Py_Initialize();
if (Py_IsInitialized()) {
PyObject* pModule = NULL;
PyObject* pFunc = NULL;
// 直接指定.py文件的位置
// PyRun_SimpleString("import sys");
// PyRun_SimpleString("sys.path.append('C:/xxxxx')");
pModule = PyImport_ImportModule("temp");
if (pModule) {
pFunc = PyObject_GetAttrString(pModule, "add");
PyObject* pArgs = PyTuple_New(2);
PyTuple_SetItem(pArgs, 0, Py_BuildValue("i", 2));
PyTuple_SetItem(pArgs, 1, Py_BuildValue("i", 3));
PyObject* pReturn = PyEval_CallObject(pFunc, pArgs);
int nResult;
PyArg_Parse(pReturn, "i", &nResult);
cout << nResult << endl;
pFunc = PyObject_GetAttrString(pModule,"hello");
PyObject* pArgs1 = PyTuple_New(1);
PyTuple_SetItem(pArgs1,0, Py_BuildValue("s", "XiaoMing"));
PyObject_CallObject(pFunc, pArgs1);
}
}
Py_Finalize();
return 0;
}
执行以上程序就可以输出如下的结果:
五.结语
博主在学习的过程中参考了如下大佬的资料:
Visual Studio中C++的包含目录、附加包含目录和库目录和附加库目录的区别
C++ 调用Python脚本
Visual Studio 2019 添加dll库
C++调用python方法及环境配置(Windows环境、VS工具)
以上便是本文的全部内容,要是觉得不错的话就点个赞或关注一下博主吧,你们的支持是博主继续创作的不解动力,当然若是有任何问题也敬请批评指正!!!
更多推荐
C++调用Python脚本学习笔记
发布评论