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/

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章