目录
0、参考文档及博客
重要的东西放前面:
用C或者C++编写模块的官方文档:
https://docs.python.org/3/extending/index.html
我配置环境时的参考博客:
https://blog.csdn.net/nanguabing007/article/details/89394541
1、环境配置步骤
这个网上有太多教程了,总结一下就是:
0、VS项目配置里,32位系统配置选x86,64位系统配置选x64,否则无法调用pyd等动态库,导致出现调用自己写的简单python代码可以,但是无法调用numpy等第三方库的问题。调用不了numpy还不会报错,只会在调用完PyImport_ImportModule函数之后返回NULL,极难调试。选Debug版就要在链接器配置里连接python36_d.lib,选release版要链接python36.lib。
1、找python源代码编译一遍,获得对应的pythonxx_d.lib文件。这里遇到过的问题主要是:(1)VS2017编译报错,显示少了个SDK。
解决方法是:用Visual Studio Installer下载所需的SDK。
2、将编译完成后出现的PcBuild->win32->python36_d.lib文件移动到相应的位置,在VS中配置好配置->链接器->输入。这里遇到的主要问题是:(1)运行调用python文件的代码时显示缺少python的动态链接文件。
解决方法是:找到python36_d.dll,移动到C:\Windows\System32文件夹下面。
3、运行调用python的C++代码。这里遇到的主要问题是:(1)调用Py_Initialize函数时,报错如下:
“Fatal Python error: Py_Initialize: unable to load the file system codec ModuleNotFoundError: No module named ‘encodings’”。
解决方法是在系统环境变量里加上这条:
网上有教程说要设置PYTHONHOME才能用,但是我试了一下,只设置PYTHONPATH就够了
注意:设置完环境变量之后要重启才能生效。
2、C++调用python的方法
代码框架:(同样来源于上面这篇博客,可用于测试环境配置成功与否)
#include <Python.h>
#include<iostream>
using namespace std;
int main()
{
Py_SetPythonHome((wchar_t*)("D:\anaconda\envs\mworks_dis"));
/**
这句语句是在添加python.exe所在路径
**/
Py_Initialize();//使用python之前,要调用Py_Initialize();这个函数进行初始化
if (!Py_IsInitialized())
{
printf("初始化失败!");
return 0;
}
else {
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");//这一步很重要,修改Python路径
PyObject * pModule = NULL;//声明变量
PyObject * pFunc = NULL;// 声明变量
pModule = PyImport_ImportModule("hello");//这里是要调用的文件名hello.py
if (pModule == NULL)
{
cout << "没找到该Python文件" << endl;
}
else {
pFunc = PyObject_GetAttrString(pModule, "add");//这里是要调用的函数名
PyObject* args = Py_BuildValue("(ii)", 28, 103);//给python函数参数赋值
PyObject* pRet = PyObject_CallObject(pFunc, args);//调用函数
int res = 0;
PyArg_Parse(pRet, "i", &res);//转换返回类型
cout << "res:" << res << endl;//输出结果
}
Py_Finalize();//调用Py_Finalize,这个根Py_Initialize相对应的。
}
return 0;
}
报错处理函数
网上给的代码里对初始化python环境以及调用python文件函数的异常处理往往都是直接输出一句话,但其实这样非常不方便我们调试。(比如每次都是在调用某个python文件的时候返回NULL值,但是如果进程崩溃在python代码运行阶段,我们是无法知道问题出在哪的)
所以我们最好写一些异常处理函数,使得我们能看到python代码报出来的错误。
(1)处理方法一:PyErr_Print
在每个可能出错的地方加上这句,这样它就会把在python虚拟机里调用时发生的错误在控制台中显示出来。我们就可以根据信息来进行调试。
if (PyErr_Occurred()) PyErr_Print();
(2)处理方法二:PyErr_Fetch
这也是网上提的很多的方法,功能比上面的要自由得多。例如你并不是在写一个控制台程序,用PyErr_Print就没有地方给你输出。或者你想要做一个错误日志记录的功能,那就可以用这个方法。
网上提到这个方法的很多,但是比较少直接给出即插即用的代码块的。我找了很久才找到这个(C代码中如何得到python脚本异常时的traceback信息)。但文中代码无法即插即用,所以我在它上面作了一些小修改,得到下面的代码块。(如果使用的时候发现PyObject 这些函数都报错,说明环境没配好,建议先按照文章第一步先把python环境配好)
#pragma once
#ifndef PYTHONEXCEPTION_H
#define PYTHONEXCEPTION_H
#include <Python.h>
#include <frameobject.h>
#include <fstream>
using namespace std;
void message_error_dialog_show(char* buf)
{
ofstream ofile;
if(ofile)
{
ofile.open("error_dialog.txt", ios::out);
ofile << buf;
ofile.close();
}
return;
}
void process_python_exception()
{
char buf[65536], *buf_p = buf;
PyObject *type_obj, *value_obj, *traceback_obj;
PyErr_Fetch(&type_obj, &value_obj, &traceback_obj);
if (value_obj == NULL)
return;
PyObject *pstr = PyObject_Str(value_obj);
const char* value = PyUnicode_AsUTF8(pstr);
size_t szbuf = sizeof(buf);
int l;
PyCodeObject *codeobj;
l = snprintf(buf_p, szbuf, ("Error Message:\n%s"), value);
buf_p += l;
szbuf -= l;
if (traceback_obj != NULL) {
l = snprintf(buf_p, szbuf, ("\n\nTraceback:\n"));
buf_p += l;
szbuf -= l;
PyTracebackObject *traceback = (PyTracebackObject *)traceback_obj;
for (; traceback && szbuf > 0; traceback = traceback->tb_next) {
codeobj = traceback->tb_frame->f_code;
l = snprintf(buf_p, szbuf, "%s: %s(# %d)\n",
PyUnicode_AsUTF8(PyObject_Str(codeobj->co_name)),
PyUnicode_AsUTF8(PyObject_Str(codeobj->co_filename)),
traceback->tb_lineno);
buf_p += l;
szbuf -= l;
}
}
message_error_dialog_show(buf);
Py_XDECREF(type_obj);
Py_XDECREF(value_obj);
Py_XDECREF(traceback_obj);
}
#endif // !PYTHONEXCEPTION_H
把这段代码粘贴到需要进行异常处理的地方就好。
2.5、终极解决方案
如果实在被调用过程搞烦了,还有一个权宜之计,直接用system(“python xxxxxxxx.py”)来调。
3、踩坑记录
按照前面的步骤应该能解决绝大多数问题了。下面这一章是我在某个特定项目中踩的坑,不一定能帮到读者。
(1)python第三方库调用出错
我在开发时调用到了两份python代码,一份只调用了os、zipfile等这些自带的库,顺利调用,另一份调用了numpy、pandas这类第三方库,然后就报错。
排查出来是两个原因导致的:python第三方库不完整(或损坏)、电脑上有加密软件之类的。
第一种情况是python第三方库不完整(或损坏),报错信息里会写某个库里缺少dll(得先配好上文第二步的PyErr_Fetch代码才能看到是哪个库的问题),这种情况就直接卸载这个库再重装就好。我开发的时候把pandas、numpy、lxml库都重装了一遍之后就好了。
第二种情况是电脑上有加密软件之类的,这种情况应该在实验室或者公司里很常见,具体原因是某些加密软件的加密策略中没有考虑到VS也有调用.py文件的需求,所以通过VS调不动.py脚本文件,也就是说VS运行python代码的时候,打开的是乱码,那自然没法进行编译运行。解决方法是更新一下加密软件的加密策略,或者把要调用的.py文件编译成dll和lib,又或者用下面的代码编译成.pyc文件(编译完之后记得把生成的.pyc文件名中多出来的.cpython36这些删掉)。
import py_compile
py_compile.compile(r'E:\xxxx.py')
(2)python模块环境太大
一开始开发的时候由于不知道python环境中哪些是要用到的,哪些是不需要的,所以把整个1.8G的python文件夹打包到模块中交付给别人(觉得自己好憨啊…)
后来经过一番查阅,发现其实只需要以下文件就可以让C++成功调用python代码了。
这里面每个文件夹的作用在文章的第一部分已经写了,一些是项目依赖项,一些是系统环境变量要求的。两个dll是备用了,如果接收的人电脑里没有这两个dll的话你可以直接给他。
(3)软件无法调用该调用了python代码的模块
我单独开发的时候,是写一个控制台demo来进行测试的,测试时可以顺利调用python,但是交付集成到别的软件中时却无法调用。这种情况建议把python代码编译成dll和lib再用。具体原因我也没懂,但确实凑效了,读者遇到这种问题可以试一试这种方法。
至于其他的坑,现在回头想想都是因为没有用上PyErr_Fetch看不到报错信息,导致无从下手调试。用上PyErr_Fetch之后,对着报错信息来处理,很多问题都迎刃而解了。
传参进python虚拟机的方式
https://www.freesion.com/article/1509186861/
标签:调用,python,代码,Py,C++,PyErr,报错 From: https://www.cnblogs.com/lidabo/p/17836764.html