C++ 调用Python脚本及其第三方库
〇、项目说明
目标是用C++调用Python脚本,该脚本中可能包含各种第三方库,需要使用对应conda环境下的Python解释器。以下将由简单到复杂展示如何使用C++调用Anaconda下的python及其第三方库(以Anaconda 的base环境为例)。
一、添加环境变量
以下PYTHONHOME和PYTHONPATH如果使用系统环境变量方式添加,会影响其他虚拟环境下python的使用,如果需要经常切换虚拟环境或不想反复改动系统环境变量,建议使用代码临时变量的方式添加。
1. PYTHONHOME
系统环境变量
N: PYTHONHOME
V: D:\ProgramData\Anaconda3
代码临时变量
#include <Windows.h>
// 设置 PYTHONHOME 环境变量
const wchar_t* PYTHONHOME_N = L"PYTHONHOME";
const wchar_t* PYTHONHOME_V = L"D:\\ProgramData\\Anaconda3";
if (!SetEnvironmentVariable(PYTHONHOME_N ,PYTHONHOME_V )) {
std::cerr << "Failed to set PYTHONHOME environment variable." << std::endl;
return 1;
}
2. PYTHONPATH
系统环境变量
N: PYTHONPATH
V: %PYTHONHOME%\Lib;%PYTHONHOME%\DLLs
临时环境变量
#include <Windows.h>
// 设置 PYTHONPATH 环境变量
const wchar_t* PYTHONPATH_N = L"PYTHONPATH";
const wchar_t* PYTHONPATH_V = L"D:\\ProgramData\\Anaconda3\\Lib;D:\\ProgramData\\Anaconda3\\DLLs";
if (!SetEnvironmentVariable(PYTHONPATH_N ,PYTHONPATH_V )) {
std::cerr << "Failed to set PYTHONHOME environment variable." << std::endl;
return 1;
}
3. base环境添加到Path
- 先建立系统环境变量Anaconda_Python_base:
N: Anaconda_Python_base
V: D:\ProgramData\Anaconda3;D:\ProgramData\Anaconda3\libs;D:\ProgramData\Anaconda3\include;D:\ProgramData\Anaconda3\Scripts;D:\ProgramData\Anaconda3\Library\bin;
再在Path中追加刚新建的环境变量:
二、 新建VS工程
工程名为 UsingPython。
添加新属性表
注意,属性表和平台对应,我将使用 Release | x64。
在对应文件夹位置右击—添加新项目属性表,我命名为Python_base_Release_x64。
接下来双击该属性表,对其进行修改。
包含目录
工程—属性—配置属性— C/C++ —常规—附加包含目录
D:\ProgramData\Anaconda3\include
库目录
工程—属性—配置属性—链接器—常规—附加库目录
D:\ProgramData\Anaconda3\libs
依赖项
工程—属性—配置属性—链接器—输入—附加依赖项
python39.lib
三、 C++调用Python
1. 执行简单python语句
使用系统环境变量,新建一个usingpython_01.cpp。
#include "Python.h"
int main()
{
Py_Initialize(); //初始化
PyRun_SimpleString("print('Hello python, i am cpp.')");
Py_Finalize(); //释放
}
执行,成功调用Python解释器进行文本输出。
2. 调用Python脚本中的函数
在代码中使用临时环境变量,新建一个usingpython_02.cpp。接下来将调用octree_utils.py里的show_octree()函数:
import numpy as np
import open3d as o3d
def show_octree(pts_path,max_depth=5):
"""通过点云求open3d的体素集"""
pts = np.loadtxt(pts_path, delimiter=" ")
# 生成点云数据
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(pts[:, :3])
# 加入颜色信息
if (len(pts[0, :]) > 5):
color = pts[:, 3:6] / 255.0
pcd.colors = o3d.utility.Vector3dVector(color) # 将颜色值归一化到[0, 1]
octree = o3d.geometry.Octree(max_depth)
octree.convert_from_point_cloud(pcd)
o3d.visualization.draw_geometries([octree])
return 1
C++代码:
#include "Python.h"
#include <iostream>
#include <string>
#include <Windows.h>
using namespace std;
int main()
{
// 设置 PYTHONHOME 环境变量
const wchar_t* PYTHONHOME_N = L"PYTHONHOME";
const wchar_t* PYTHONHOME_V = L"D:\\ProgramData\\Anaconda3";
if (!SetEnvironmentVariableW(PYTHONHOME_N ,PYTHONHOME_V )) {
std::cerr << "Failed to set PYTHONHOME environment variable." << std::endl;
return 1;
}
// 设置 PYTHONPATH 环境变量
const wchar_t* PYTHONPATH_N = L"PYTHONPATH";
const wchar_t* PYTHONPATH_V = L"D:\\ProgramData\\Anaconda3\\Lib;D:\\ProgramData\\Anaconda3\\DLLs";
if (!SetEnvironmentVariableW(PYTHONPATH_N ,PYTHONPATH_V )) {
std::cerr << "Failed to set PYTHONHOME environment variable." << std::endl;
return 1;
}
Py_Initialize(); // 初始化
// 将py文件所在路径添加到搜索路径中
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('./')");
// 加载模块
PyObject* pModule = PyImport_ImportModule("octree_utils"); //文件名 octree_utils.py
if (!pModule)
{
cout << "[ERROR] Python get module failed." << endl;
return 0;
}
else {
cout << "[INFO] Python get module succeed." << endl;
}
// 加载函数
PyObject* pv = PyObject_GetAttrString(pModule, "show_octree");
if (!pv || !PyCallable_Check(pv))
{
cout << "[ERROR] Can't find funftion." << endl;
return 0;
}
cout << "[INFO] Get function succeed." << endl;
// 准备参数
const char* arg1 = "D:\\Project\\CPP_Project\\UsingPython\\UsingPython\\module\\library.txt";
int arg2 = 5;
// 将参数转换为 Python 对象
PyObject* pArgs = PyTuple_New(2);
PyTuple_SetItem(pArgs, 0, PyUnicode_FromString(arg1));
PyTuple_SetItem(pArgs, 1, PyLong_FromLong(arg2));
// 调用函数
PyObject* pRet = PyObject_CallObject(pv, pArgs);
// 获取参数
if (pRet) // 验证是否调用成功
{
long result = PyLong_AsLong(pRet);
cout << "result:" << result;
}
Py_Finalize(); // 释放资源
return 0;
}
运行cpp代码,用C++调用python中open3d库计算八叉树并显示。
3. 调用整个python脚本
接下来将调用整个python脚本,其实就是相当于开一个终端执行脚本。
python文件内容如下,功能为打印传入的参数:
import sys
def printArgs():
for arg in sys.argv:
print(arg)
if __name__ == '__main__':
printArgs()
在代码中使用临时环境变量,新建一个usingpython_03.cpp,内容如下:
#include "Python.h"
#include <iostream>
#include <string>
#include <Windows.h>
#include <cwchar>
using namespace std;
int main()
{
// 设置 PYTHONHOME 环境变量
const wchar_t* PYTHONHOME_N = L"PYTHONHOME";
const wchar_t* PYTHONHOME_V = L"D:\\ProgramData\\Anaconda3";
if (!SetEnvironmentVariableW(PYTHONHOME_N ,PYTHONHOME_V )) {
std::cerr << "Failed to set PYTHONHOME environment variable." << std::endl;
return 1;
}
// 设置 PYTHONPATH 环境变量
const wchar_t* PYTHONPATH_N = L"PYTHONPATH";
const wchar_t* PYTHONPATH_V = L"D:\\ProgramData\\Anaconda3\\Lib;D:\\ProgramData\\Anaconda3\\DLLs";
if (!SetEnvironmentVariableW(PYTHONPATH_N ,PYTHONPATH_V )) {
std::cerr << "Failed to set PYTHONHOME environment variable." << std::endl;
return 1;
}
Py_Initialize();
PyRun_SimpleString("import os");
//执行调用脚本文件命令,注意文件的路径
if (PyRun_SimpleString("os.system(('python ./octree_utils.py usingPython 20230517'))") == NULL)
{
return -1;
}
Py_Finalize();
return 0;
}
测试成功。
4. C++调用python打包的exe
这种方法跟调用其他exe一样,在这里暂不展开,放一种常用方法:
#include <Windows.h>
ShellExecute(NULL,L"open",L"./test.exe",NULL,NULL,SW_SHOW);
四、 其他
const char* 转wchar_t *
由于C++调用Python时很多API的参数类型为wchar_t*,所以我让chatGPT写了一个const char* 转wchar_t *的函数。
#include <iostream>
#include <string>
#include <Windows.h>
#include <cwchar>
using namespace std;
wchar_t* ConvertToWideString(const char* str) {
// 获取所需的宽字符长度
size_t length = mbstowcs(nullptr, str, 0);
if (length == static_cast<size_t>(-1)) {
// 错误处理
return nullptr;
}
// 分配足够的内存来存储宽字符
wchar_t* wideStr = new wchar_t[length + 1];
// 执行转换
if (mbstowcs(wideStr, str, length + 1) == static_cast<size_t>(-1)) {
// 错误处理
delete[] wideStr;
return nullptr;
}
return wideStr;
}
各类报错
对遇到部分报错记录了一下,基本上按以上方法设置完成后都可以避免。
- Fatal Python error…
Python path configuration:
PYTHONHOME = (not set)
PYTHONPATH = (not set)
program name = 'python'
isolated = 0
environment = 1
user site = 1
import site = 1
sys._base_executable = 'D:\\Project\\CPP_Project\\UsingPython\\x64\\Release\\UsingPython.exe'
sys.base_prefix = 'D:\\ProgramData\\Anaconda3'
sys.base_exec_prefix = 'D:\\ProgramData\\Anaconda3'
sys.platlibdir = 'lib'
sys.executable = 'D:\\Project\\CPP_Project\\UsingPython\\x64\\Release\\UsingPython.exe'
sys.prefix = 'D:\\ProgramData\\Anaconda3'
sys.exec_prefix = 'D:\\ProgramData\\Anaconda3'
sys.path = [
'D:\\ProgramData\\Anaconda3\\python39.zip',
'.\\DLLs',
'.\\lib',
'D:\\Project\\CPP_Project\\UsingPython\\x64\\Release',
]
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'
解决方案
见以上PYTHONHOME及PYTHONPATH环境变量设置,建议使用代码临时变量。
- UserWarning: mkl-service package failed to import…
D:\ProgramData\Anaconda3\Lib\site-packages\numpy\__init__.py:148: UserWarning: mkl-service package failed to import, therefore Intel(R) MKL initialization ensuring its correct out-of-the box operation under condition when Gnu OpenMP had already been loaded by Python process is not assured. Please install mkl-service package, see http://github.com/IntelPython/mkl-service
from . import _distributor_init
解决方案
见以上base环境添加到Path
- Traceback …
Traceback (most recent call last):
File "<string>", line 1, in <module>
NameError: name 'execfile' is not defined
解决方案
这个其实是python3删除了execfile方法,改用exec方法,但这个方法需要先把代码读下来再执行,所以我在上面调用整个python脚本使用了另一种os.system()方法。