C++调用python踩坑记录

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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值