本文主要介绍一下如何使用 C++ 调用 Python。包括运行 python 脚本;C++ 和 Python 之间参数的相互转换和传递。
下面是笔者python 相关的目录,可参考
python.exe
所在目录:A:\Programs\Python\Python11_4
python311.lib
所在目录:A:\Programs\Python\Python11_4\libs
include
所在目录:A:\Programs\Python\Python11_4\include
Python 库函数 PyRun_SimpleString 可以执行字符串形式的Python代码。不过在使用 PyRun_SimpleString 之前需要先初始化(Py_Initialize()
),执行完之后需要释放资源Py_Finalize()
,示例:
Py_Initialize();
PyRun_SimpleString("print('Hello!')");
Py_Finalize();
有待总结和补充,可看下面的实例。
所用到的几个文件组织结构如下
test
│ CMakeLists.txt
│ hello.py
│ main.cpp
└─build
hello.py
中有两个小函数,内容如下:
def add(a,b):
return a+b
def get_name(first):
return "your name is {} alice".format(first)
main.cpp
文件内容如下:
#include <iostream>
#include <Python.h>
using namespace std;
const int kError = -1;
const int kSuccess = 0;
/**
* @brief Initializes the python interpreter
*/
int pythonInit() {
Py_Initialize();
int ret = Py_IsInitialized();
if (ret == 0) {
cout << "Py_Initialize error" << endl;
return kError;
}
return kSuccess;
}
/**
* @brief Release resources requested by Python
*/
void pythonCleanup() {
Py_Finalize();
}
/**
* @brief Import module ${name} in ${pyDir}
* @param[in] pyDir The path of the python script
* @param[in] name The name of the python script
* @return the module object
*/
PyObject *pythonImportModule(const char *pyDir, const char *name) {
// 引入当前路径,否则下面模块不能正常导入
char tempPath[256] = {};
sprintf(tempPath, "sys.path.append('%s')", pyDir);
PyRun_SimpleString("import sys");
//PyRun_SimpleString("sys.path.append('./')");
PyRun_SimpleString(tempPath);
PyRun_SimpleString("print('curr sys.path', sys.path)");
// import ${name}
PyObject *module = PyImport_ImportModule(name);
if (module == nullptr) {
PyErr_Print();
cout << "PyImport_ImportModule '" << name << "' not found" << endl;
return nullptr;
}
return module;
}
/**
* @brief Call 'add' function in the python script
* @param[in] module The name of module
* @param[in] a The value of a
* @param[in] b The value of b
* @return a + b
*/
int callPythonAdd(PyObject *module, int a, int b) {
//获取模块字典属性
PyObject *pDict = PyModule_GetDict(module);
if (pDict == nullptr) {
PyErr_Print();
std::cout << "PyModule_GetDict error" << std::endl;
return kError;
}
//直接获取模块中的函数
PyObject *addFunc = PyDict_GetItemString(pDict, "add");
if (addFunc == nullptr) {
std::cout << "PyDict_GetItemString 'add' not found" << std::endl;
return kError;
}
// 构造python 函数入参, 接收2
// see: https://docs.python.org/zh-cn/3.7/c-api/arg.html?highlight=pyarg_parse#c.PyArg_Parse
PyObject *pArg = Py_BuildValue("(i,i)", a, b);
//调用函数,并得到 python 类型的返回值
PyObject *result = PyEval_CallObject(addFunc, pArg);
int ret = 0;
//将python类型的返回值转换为c/c++类型
PyArg_Parse(result, "i", &ret);
return ret;
}
/**
* @brief Call 'get_name' function in the python script
* @param[in] module The name of module
* @param[in] firstName The firstname
* @param[in] outName The fullname
* @return success or not
*/
int callPythonGetName(PyObject *module, std::string firstName, std::string &outName) {
//获取模块字典属性
PyObject *pDict = PyModule_GetDict(module);
if (pDict == nullptr) {
PyErr_Print();
std::cout << "PyModule_GetDict error" << std::endl;
return kError;
}
//直接获取模块中的函数
PyObject *addFunc = PyDict_GetItemString(pDict, "get_name");
if (addFunc == nullptr) {
std::cout << "PyDict_GetItemString 'add' not found" << std::endl;
return kError;
}
// 构造python 函数入参, 接收2
// see: https://docs.python.org/zh-cn/3.7/c-api/arg.html?highlight=pyarg_parse#c.PyArg_Parse
PyObject *pArg = Py_BuildValue("(s)", firstName.c_str());
//调用函数,并得到python类型的返回值
PyObject *result = PyEval_CallObject(addFunc, pArg);
char *name = nullptr;
//将python类型的返回值转换为c/c++类型
PyArg_Parse(result, "s", &name);
char tempStr[256] = {};
int strLen = strlen(name);
if (strLen > 256) {
return kError;
}
strcpy(tempStr, name);
outName = tempStr;
return kSuccess;
}
int main() {
pythonInit();
//直接运行python代码
PyRun_SimpleString("print('---------- Hello Python form C/C++ ----------')");
PyObject *helloModule = pythonImportModule("../", "hello"); // 这里最好还是给绝对路径吧
if (helloModule == nullptr) {
return -1;
}
// call python add function
int result = callPythonAdd(helloModule, 1, 3);
cout << "1 + 3 = " << result << endl;
// call python get_name function
std::string fullName;
callPythonGetName(helloModule, "summer", fullName);
cout << fullName << endl;
pythonCleanup();
}
CMakeLists.txt 文件内容等会儿说;build 是一个空文件,生成的二进制文件等放这里面。
1、首先新建一个工程,将main.cpp
添加进去,然后将hello.py
放在和main.cpp
一样的路径下。
2、将 IDE 上方『解决方案平台』设置为x64
,最好将『解决方案配置』设置为Release
(debug 需要*_d.lib
)
3、将include
添加到 C/C++ 附加包含目录中:项目右键→属性→C/C++→附加包含目录→添加 python 的 include
右键属性→链接器→常规→附加库目录→将 python 的 libs 加进去
3、将*.lib
添加到附加依赖项中:项目右键→属性→链接器→输入→将python311.lib
和python311_d.lib
加进去。
cmake 自动搜寻 python
CMakeLists.txt
文件内容如下:
cmake_minimum_required( VERSION 3.20 )
project( test )
set( PRJ_INCLUDE_DIRS )
set( PRJ_COMPILE_FEATURES )
set( PRJ_LIBRARIES )
list( APPEND PRJ_COMPILE_FEATURES cxx_std_20 )
find_package(Python3 COMPONENTS Interpreter Development)
message( STATUS "Python3_FOUND = ${Python3_FOUND} " )
message( STATUS "Python3_INCLUDE_DIRS = ${Python3_INCLUDE_DIRS} " )
message( STATUS "Python3_LIBRARIES = ${Python3_LIBRARIES} " )
if( ${Python3_FOUND} )
#include_directories(${Python3_INCLUDE_DIRS})
else()
message( FATAL_ERROR "Python3 not found, please install it." )
endif()
list( APPEND PRJ_INCLUDE_DIRS ${Python3_INCLUDE_DIRS} )
list( APPEND PRJ_LIBRARIES ${Python3_LIBRARIES} )
message( STATUS "PRJ_LIBRARIES = ${PRJ_LIBRARIES} " )
add_executable( ${PROJECT_NAME}
main.cpp
)
target_include_directories( ${PROJECT_NAME}
PRIVATE
${PRJ_INCLUDE_DIRS}
)
target_link_libraries( ${PROJECT_NAME}
PRIVATE
${PRJ_LIBRARIES}
)
target_compile_features( ${PROJECT_NAME}
PRIVATE
${PRJ_COMPILE_FEATURES}
)
用 CMake 编译一下
然后直接跑就可以!
cmake 手动设置 Python 路径
CMakeLists.txt
文件内容如下:
cmake_minimum_required( VERSION 3.20 )
project( test )
set( PRJ_INCLUDE_DIRS )
set( PRJ_COMPILE_FEATURES )
set( PRJ_LIBRARIES )
list( APPEND PRJ_COMPILE_FEATURES cxx_std_20 )
set( Python3_INCLUDE_DIRS "A:/Programs/Python/Python11_4/include")
set( Python3_LIBRARIES "A:/Programs/Python/Python11_4/libs/python311.lib"
"A:/Programs/Python/Python11_4/libs/python311_d.lib" )
message( STATUS "Python3_INCLUDE_DIRS = ${Python3_INCLUDE_DIRS} " )
message( STATUS "Python3_LIBRARIES = ${Python3_LIBRARIES} " )
list( APPEND PRJ_INCLUDE_DIRS ${Python3_INCLUDE_DIRS} )
list( APPEND PRJ_LIBRARIES ${Python3_LIBRARIES} )
message( STATUS "PRJ_LIBRARIES = ${PRJ_LIBRARIES} " )
add_executable( ${PROJECT_NAME}
main.cpp
)
target_include_directories( ${PROJECT_NAME}
PRIVATE
${PRJ_INCLUDE_DIRS}
)
target_link_libraries( ${PROJECT_NAME}
PRIVATE
${PRJ_LIBRARIES}
)
target_compile_features( ${PROJECT_NAME}
PRIVATE
${PRJ_COMPILE_FEATURES}
)
---------- Hello Python form C/C++ ----------
curr sys.path ['A:\\Programs\\Python\\Python11_4\\python311.zip', 'A:\\Programs\\Python\\Python11_4\\DLLs', 'A:\\Programs\\Python\\Python11_4\\Lib', 'A:\\aWork\\scripts\\test1\\test\\build\\Debug', 'A:\\Programs\\Python\\Python11_4', 'A:\\Programs\\Python\\Python11_4\\Lib\\site-packages', '../']
1 + 3 = 4
your name is summer alice
无法打开 python311_d.lib
的问题:笔者使用的 python 版本是 Python 11.4,它的libs
中没有python311_d.lib
,只有python311.lib
(因为安装的时候没有勾选安装 debug 的 lib),解决方法有三个:
debug
模式运行程序,使用release
或其他模式运行 C++ 程序pyconfig.h
文件(一般在py_dir/include
文件夹下),注释下面的内容(有点危险)#ifdef _DEBUG
# define Py_DEBUG
#endif
Download debug binaries (requires VS 2017 or later)
这种情况下是没有把 python 脚本所在的路径加到sys.path
里面,使用sys.path.append(your_path_xx)
添加一下就可以了。
报错内容:
'PyEval_CallObjectWithKeywords': deprecated in 3.9
将PyEval_CallObject
替换为PyObject_CallObject
就行了。