- 检测python解释器
- Find\.cmake文件在哪?
- 如何使用find_package()命令找不在“系统标准位置”的包?
- 嵌入执行一个python脚本文件而不是一条单一的python语句
- 检测python库
- 检测python模块和包
本部分与python相关~
检测python解释器这部分直接在CmakeLists.txt中进行python代码的嵌入,没有C/C++相关的源码,如下所示:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-01 LANGUAGES NONE)
# 这个命令要我们找到python解释器, 即:PythonInterp
find_package(PythonInterp REQUIRED)
// find_package()是一个用于发现和设置包的CMake命令,这些模块包含CMake命令,用于标识系统标准位置中的包
// 这里注意了!!!识别的是 “ 系统标准位置 ” 的包
// CMake模块文件称为 Find.cmake ,当调用 find_package() 时,模块中的命令将会运行。
// 即写find_package()命令的时候, 在系统标准位置处的 Find.cmake 文件将会执行
// 另外,查找模块还设置了一些有用的变量从而反应实际找到了什么,引入这个模块后,就可以在CMakeLists中使用这些变量
// 这些变量实际上是在Find.cmake文件中设置的
// 对于FindPythonInterp.cmake附带有如下CMake变量
# PYTHONINTERP_FOUND:是否找到解释器
# PYTHON_EXECUTABLE:Python解释器到可执行文件的路径
# PYTHON_VERSION_STRING:Python解释器的完整版本信息
# PYTHON_VERSION_MAJOR:Python解释器的主要版本号
# PYTHON_VERSION_MINOR:Python解释器的次要版本号
# PYTHON_VERSION_PATCH:Python解释器的补丁版本号
// 可以强制CMake,查找特定版本的包。例如,要求Python解释器的版本大于或等于2.7
// 使用 find_package(PythonInterp 2.7)
// find_package(PythonInterp REQUIRED)表示强制依赖关系,即找不到这个包CMake就会终止
// 但是我还有一个问题就是:如何知道Find.cmake的具体位置呢?
// Find.cmake又是如何查找相应的包是否存在呢?
// 前后文中都有说明:如果软件包没有安装在标准位置,CMake就无法定位它们, 但是用户可以使用-D选项传递一个目录给cmake
// 告诉CMake具体的查找位置,如:查找python指定:$ cmake -D PYTHON_EXECUTABLE=/custom/location/python ..
// !!!这里是一个重点,因为大多数情况下,我们都需要在自定义的目录进行库文件的部署,此时如果还想使用find_package命令
// 则需要使用这种方法进行验证!
// 这里相当于写了一个python脚本,执行一个python命令并返回其输出值
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')" // 这句话的含义就是调用输出一个python语句,
// 后面是执行以上的python命令并且捕获它的结果值和输出值
// 很明显,这里就相当于是在命令行执行一句 $ python '-c' "print('Hello, world!')"
// 注意这里的引号和 -c 都是必要的!-c 表示后面将要执行的是一个python语句而非一个文件
// 加引号也同样代表了将要执行的是一个字符串语句
RESULT_VARIABLE _status // 将 RESULT_VARIABLE 的值赋值给我们定义的变量_status
OUTPUT_VARIABLE _hello_world // 将 OUTPUT_VARIABLE 的值赋值给我们定义的变量_hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "RESULT_VARIABLE is: ${_status}") // 最后打印python的命令
message(STATUS "OUTPUT_VARIABLE is: ${_hello_world}")
// compare the "manual" messages with the following handy helper
include(CMakePrintHelpers) // 这里引入调试工具包,否则下文无法调用cmake_print_variables这个API
cmake_print_variables(_status _hello_world)
// 这句话将打印:_status="0" ; _hello_world="Hello, world!"
// 其实就是另一种打印方式,与以上的message语句功能重合
在写这个代码块的时候难免想到一些问题:
- find_package() 这个命令是通过调用 Find
.cmake这个文件来间接实现的,那么这个文件到底在哪(我们可以先不管它做了什么)? - 以上的文中提到:如果不是“系统标准位置”的包,直接使用find_package()会导致无法找到其目录。那么对于我们自定义安装的包,如何使用find_package()来找到它呢?
- 在这个文件中,我们通过嵌入一条python语句来实现其对应语句的功能,那么随之而来想到的就是:我们可不可以通过这种方式来内嵌一个完整的python文件来执行呢?
如果cmake是默认安装,则其对应的Find
另外,执行find_package()命令会优先从项目根目录(与CMakeList.txt同级目录)下的Module文件夹(如果有的话)中寻找对应的Find
我们知道,其实find_package()对应执行的Find
# PYTHONINTERP_FOUND:是否找到解释器
# PYTHON_EXECUTABLE:Python解释器到可执行文件的路径
# PYTHON_VERSION_STRING:Python解释器的完整版本信息
# PYTHON_VERSION_MAJOR:Python解释器的主要版本号
# PYTHON_VERSION_MINOR:Python解释器的次要版本号
# PYTHON_VERSION_PATCH:Python解释器的补丁版本号
我们可以通过直接给cmake传入这些参数来解决问题,从而不必使用find_package()命令,如下指定python解释器的位置(其实就是python命令可执行文件的位置):
$ cmake -D PYTHON_EXECUTABLE=/custom/location/python ..
但是这样显然不是我想要的答案,因为我们相当于绕开了find_package()指令,并没有执行Find
比较简单,修改上述对应部分如下:
set(Py_File_Path "/home/ctrtemp/Documents/VS_Code_Prj/Learning/Learning_Cmake/Own_Learning/chapter-03/recipe-01/example/test.py")
// set(Py_File_Path "../test.py")
// 以上使用相对路径或绝对路径来指定要执行的python文件
execute_process(
COMMAND
${PYTHON_EXECUTABLE} ${Py_File_Path}
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
在build文件夹下执行cmake … 将会得到和以上相同的结果
但我仍然有问题:这其实并不是在C++中内嵌了python代码,而是在代码编译阶段执行了python脚本文件,和我预想的“使用C++调用执行python”还有一定的偏差!(Pending:2022-04-26)
检测python库我们先来看一下源文件的做法:
#include
int main(int argc, char *argv[]) {
Py_SetProgramName(argv[0]); /* optional but recommended */
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");
Py_Finalize();
return 0;
}
其大概意思就是通过给PyRun_SimpleString()函数传一个字符串格式的python语句,并将其结果输出。注意一个点:这里的Python.h在编辑器(我的是VScode)中是会直接报错的。警告说找不到这个文件,但是执行cmak语句却可以生成对应的可执行文件,这是如何做到的呢?
其实很多工程文件都是如此!以下几点必须明确:
- 在代码编辑器中,是否给出这种警报一般是编辑器检索系统环境变量中的路径,如果头文件没有出现在环境变量指定的路径下,则会报错说找不到(一般对于C/C++来说,头文件对应的环境变量大多为:C_Path / C_INCLUDE_PATH / CPLUS_INCLUDE_PATH)
- 一般工程需要很多的依赖文件,包括头文件以及库文件,如果用户想部署工程,并通过当场修改环境变量以适应工程的话是非常繁琐的,并且将会使得整个系统变量变得繁杂(就因为一个工程做很大修改既麻烦又造成了污染!)
- 所以一般是通过编辑CMakeLists.txt的方式,动态地为工程指定其对应的依赖文件的路径,相当于为工程进行“定制”。
于是有以下的CMakeList.txt部分代码:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-02 LANGUAGES C)
set(CMAKE_C_STANDARD 99) // 执行C99标准
set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_C_STANDARD_REQUIRED ON)
find_package(PythonInterp REQUIRED)
message(STATUS "${PYTHON_VERSION_MAJOR}")
message(STATUS "${PYTHON_VERSION_MINOR}")
// 我们在这里进行打印一次!以此印证一些变量是通过findpackage来进行设置的
// 这里很有必要进行记录,以说明find_package的作用,并且,你应该适当的看一看findXXX.cmake的源文件
// 看看他们是如何寻找并改变变量的
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
add_executable(hello-embedded-python hello-embedded-python.c)
message(STATUS "${PYTHON_INCLUDE_DIRS}")
message(STATUS "${PYTHON_LIBRARIES}")
target_include_directories(hello-embedded-python
PRIVATE
${PYTHON_INCLUDE_DIRS}
)
target_link_libraries(hello-embedded-python
PRIVATE
${PYTHON_LIBRARIES}
)
以上代码尤其注意关于message对应的语句,输出依赖文件对应的位置,显然,这些变量是被find_package()找到并设置的。同样的,在头文件以及库文件对应的那几个环境变量中,没有包含其对应的路径,这也正是之前在代码编辑器中报错的原因。也正因为是我们通过CmakeLists.txt设置了对应的变量,才使得我们可以在编译的过程中索引到对应依赖文件,成功生成并执行可执行文件。
检测python模块和包之前提到的是在CMakeLists.txt中嵌入了python语句或者文件并执行,但并没有满足我们的实际需求:我们实际想做的是在C/C++中直接内嵌python代码或用C/C++调用python脚本文件执行。这一节就是解决我们之前的需求的,在C/C++中内嵌执行python。
首先我们来看C++代码部分,基本的代码已经做了相对应的注释说明:
#include
#include
using namespace std;
//注意这里我们要输入一些参数了
int main(int argc, char *argv[]) {
PyObject *pName, *pModule, *pDict, *pFunc;
PyObject *pArgs, *pValue;
int i;
if (argc < 3) { // 如果参数过少则出现这个警告
fprintf(stderr, "Usage: pure-embedding pythonfile funcname [args]\n");
return 1;
}
// 应该输入的是四个参数,包括
// 1:所调用的文件名
// 2:所调用文件中的函数名
// 3/4:函数需要的两个传入的参数
// 但实际上包括五个参数,其中第一个参数是可执行文件其本身
// 故argc总比实际参数多一个
// cout<<"argc = "<
// cout<<"argv 0 = "<
// cout<<"argv 1 = "<
// cout<<"argv 2 = "<
// cout<<"argv 3 = "<
// cout<<"argv 4 = "<
Py_Initialize();
PyRun_SimpleString("import sys");//用这种字符串的形式在C++中内嵌运行python代码
PyRun_SimpleString("sys.path.append(\".\")");
pName = PyUnicode_DecodeFSDefault(argv[1]);//这里是python文件名
/* Error checking of pName left out */
pModule = PyImport_Import(pName);
Py_DECREF(pName);
if (pModule != NULL) {
pFunc = PyObject_GetAttrString(pModule, argv[2]);//这里是调用函数名
/* pFunc is a new reference */
if (pFunc && PyCallable_Check(pFunc)) {
pArgs = PyTuple_New(argc - 3);
for (i = 0; i < argc - 3; ++i) {
pValue = PyLong_FromLong(atoi(argv[i + 3]));//这里传入两个函数参数
if (!pValue) {
Py_DECREF(pArgs);
Py_DECREF(pModule);
fprintf(stderr, "Cannot convert argument\n");
return 1;
}
/* pValue reference stolen here: */
PyTuple_SetItem(pArgs, i, pValue);
}
pValue = PyObject_CallObject(pFunc, pArgs);
Py_DECREF(pArgs);
if (pValue != NULL) {
printf("Result of call: %ld\n", PyLong_AsLong(pValue));
Py_DECREF(pValue);
} else {
Py_DECREF(pFunc);
Py_DECREF(pModule);
PyErr_Print();
fprintf(stderr, "Call failed\n");
return 1;
}
} else {
if (PyErr_Occurred())
PyErr_Print();
fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
}
Py_XDECREF(pFunc);
Py_DECREF(pModule);
} else {
PyErr_Print();
fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
return 1;
}
Py_Finalize();
return 0;
}
执行时输入的参数如下:
$ ./pure-embedding use_numpy print_ones 2 3
之后来看CMake_Lists.txt文件:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-03 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(PythonInterp REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import re, numpy; print(re.compile('/__init__.py.*').sub('',numpy.__file__));"
// 执行一个python语句 导入numpy,re模块, 并打印出numpy这个包的位置
// 这次的脚本较长,并且对python的内部库进行了引入 ———— 引入了一些模块
RESULT_VARIABLE _numpy_status // 如果找到了numpy这个包, 则_numpy_status将被设置为一个整数,否则为错误的字符串
OUTPUT_VARIABLE _numpy_location // 还是用两个变量对结果变量和输出变量进行了承接
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT _numpy_status) // 如果没有找到 _numpy_location 将包含Numpy的路径
set(NumPy ${_numpy_location} CACHE STRING "Location of NumPy")
// NumPy是个新变量,它的值是_numpy_location的字符串, 但是后面的 CACHE STRING 是什么意思呢?
endif()
// 获取 Numpy的模块版本
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import numpy; print(numpy.__version__)"
OUTPUT_VARIABLE _numpy_version
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "curren numpy version = ${_numpy_version}")
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(NumPy
FOUND_VAR NumPy_FOUND // 设置Numpy_FOUND变量以及其他的输出信息
REQUIRED_VARS NumPy
VERSION_VAR _numpy_version
)
add_executable(pure-embedding "")
target_sources(pure-embedding
PRIVATE
Py${PYTHON_VERSION_MAJOR}-pure-embedding.cpp
)
target_include_directories(pure-embedding
PRIVATE
${PYTHON_INCLUDE_DIRS}
)
target_link_libraries(pure-embedding
PRIVATE
${PYTHON_LIBRARIES}
)
add_custom_command( # 这里没有理解?!
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py
)
# make sure building pure-embedding triggers the above
# custom command
target_sources(pure-embedding
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
)
首先这一部分的重点在于我们要理解add_custom_command()这个语句:
- 语句允许我们在代码中执行某些用户自定义的命令,相当于直接在命令行执行语句块中的一些命令
- 关于参数方面:COMMAND后面加一个所要输出的命令
- 之后的参数比较繁杂这里暂时pending
(Pending 2022-04-26)
感觉这里要较长期间的pending了,遇到一个真正需要C/C++嵌入python代码的应用时,用到之后才能更快更好的进行理解提高。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)