• Visual Studio 2019下使用C++与Python进行混合编程——环境配置与C++调用Python API接口


    前言

    1. 在vs2019下使用C++与Python进行混合编程,在根源上讲,Python 本身就是一个C库,那么这里使用其中最简单的一种方法是把Python的C API来嵌入C++项目中,来实现混合编程。
    2. 当前的环境是,win10,IDE是vs2019,python版本是3.9,python的环境是使用Anacond安装的。

    一、环境配置

    1. 安装Python
    首先要安装好Python的库,Python可以直接从官网下载,或者直接在conda里面进行安装。

    2.添加环境变量
    安装完成之后,添加两个系统环境变量,分别是:PYTHONHOME和PYTHONPATH。
    在这里插入图片描述
    如果不添加这两个系统环境变量会报以下的错误:

    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 = 'C:\\code\\cpp\\PDFToDoc\\x64\\Release\\PDFToDoc.exe'
      sys.base_prefix = 'C:\\Users\\duole\\anaconda3'
      sys.base_exec_prefix = 'C:\\Users\\duole\\anaconda3'
      sys.platlibdir = 'lib'
      sys.executable = 'C:\\code\\cpp\\PDFToDoc\\x64\\Release\\PDFToDoc.exe'
      sys.prefix = 'C:\\Users\\duole\\anaconda3'
      sys.exec_prefix = 'C:\\Users\\duole\\anaconda3'
      sys.path = [
        'C:\\Users\\duole\\anaconda3\\python39.zip',
        '.\\DLLs',
        '.\\lib',
        'C:\\code\\cpp\\PDFToDoc\\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'
    
    Current thread 0x000042d4 (most recent call first):
    <no Python frame>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    3. 创建项目
    打开vs2019,创建一个空的新C++项目:
    在这里插入图片描述
    创建完成后打开项目属于配置包含目录与库目录:
    在这里插入图片描述
    在附加依赖项目里把python的lib库名添加到里面:
    在这里插入图片描述
    4.添加代码
    在项目里面新添一个main.cpp
    在这里插入图片描述
    main.cpp里面的代码:

    #include 
    
    int main()
    {
        Py_Initialize();    // 初始化python解释器
        PyRun_SimpleString("print('hello python')");
        Py_Finalize();      // 释放资源
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    然后运行项目
    在这里插入图片描述
    这样配置就算法成功了。

    二、Python C API 调用

    为了方便项目测试,在项目根目录下添加一个script目录,在script目录里面创建一个call_python.py的文件。
    在这里插入图片描述

    2.1 调用Python代码无参函数

    C++调用python无参函数流程:

    1. 初始化python接口(Py_Initialize)
    2. 导入依赖库 (PyRun_SimpleString)
    3. 初始化python系统文件路径(PyRun_SimpleString)
    4. 调用python文件名(PyImport_ImportModule)
    5. 获取函数对象(PyObject_GetAttrString)
    6. 调用函数对象(PyObject_CallObject)
    7. 结束python接口调用,释放资源(Py_Finalize)

    在call_python.py里面添加代码:

    def test():
        print("hello python to C++")
    
    • 1
    • 2

    然后在main.cpp里面进行调用:

    int main()
    {
    	//1.初始化python接口
    	Py_Initialize();
    	if (!Py_IsInitialized)
    	{
    		std::cout << "python init failed" << std::endl;
    		return 1;
    	}
    
    	//2.导入依赖库
    	PyRun_SimpleString("import sys");//执行py单条语句
    
    	//3.初始化python系统文件路径,以便访问到python源码文件所在的路径
    	PyRun_SimpleString("sys.path.append('./script')");
    
    	//4.调用python源码文件,只写文件名,不用写后缀
    	PyObject* module = PyImport_ImportModule("call_python");
    
    	if (module == nullptr)
    	{
    		std::cout << "module not found: call_python" << std::endl;
    		return 1;
    	}
    
    	//5.获取python文件里面的函数
    	PyObject* test = PyObject_GetAttrString(module, "test");
    
    	if (!test || !PyCallable_Check(test))
    	{
    		std::cout << "function not found: test" << std::endl;
    		return 1;
    	}
    
    	//6.调用函数,函数对象与传入参数
    	PyObject_CallObject(test, nullptr);
    	
    	Py_Finalize();
    
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    2.2 调用Python代码有参与有返回值的函数

    C++调用python有参并有返回的函数流程:

    1. 初始化python接口(Py_Initialize)
    2. 导入依赖库 (PyRun_SimpleString)
    3. 初始化python系统文件路径(PyRun_SimpleString)
    4. 调用python文件名(PyImport_ImportModule)
    5. 获取函数对象(PyObject_GetAttrString)
    6. 传递参数(PyTuple_New,Py_BuildValue)
    7. 调用函数对象(PyObject_CallObject)
    8. 接收函数返回值(PyArg_Parse)
    9. 结束python接口初始化(Py_Finalize)

    在call_python.py里面添加代码:

    def add(a, b):
        c = a + b
        print(f"{a} + {b} = {c}")
        return c
    
    • 1
    • 2
    • 3
    • 4

    然后在main.cpp里面进行调用:

    #include 
    #include 
    
    int main()
    {
        // 1、初始化python接口
        Py_Initialize();
        if (!Py_IsInitialized())
        {
            std::cout << "python init failed" << std::endl;
            return 1;
        }
    
        // 2、初始化python系统文件路径,保证可以访问到 .py文件
        PyRun_SimpleString("import sys");
        PyRun_SimpleString("sys.path.append('./script')");
    
        // 3、调用python文件名,不用写后缀
        PyObject* module = PyImport_ImportModule("call_python");
        if (module == nullptr)
        {
            std::cout << "module not found: call_python" << std::endl;
            return 1;
        }
        // 4、调用函数
        PyObject* func = PyObject_GetAttrString(module, "add");
        if (!func || !PyCallable_Check(func))
        {
            std::cout << "function not found: add" << std::endl;
            return 1;
        }
    
        // 5、给 python 传递参数
        // 函数调用的参数传递均是以元组的形式打包的, 2表示参数个数
        // 如果函数中只有一个参数时,写1就可以了
        PyObject* args = PyTuple_New(2);
    
        // 0:第一个参数,传入 int 类型的值 1
        PyTuple_SetItem(args, 0, Py_BuildValue("i", 1));
        // 1:第二个参数,传入 int 类型的值 2
        PyTuple_SetItem(args, 1, Py_BuildValue("i", 2));
    
        // 6、使用C++的python接口调用该函数
        PyObject* ret = PyObject_CallObject(func, args);
    
        // 7、接收python计算好的返回值
        int result;
        // i表示转换成int型变量。
        // 在这里,最需要注意的是:PyArg_Parse的最后一个参数,必须加上“&”符号
        PyArg_Parse(ret, "i", &result);
        std::cout << "return is " << result << std::endl;
    
        // 8、结束python接口初始化
        Py_Finalize();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    Py_BuildValue()函数的作用和PyArg_ParseTuple()的作用相反,它是将C类型的数据结构转换成Python对象,该函数的原型:
    PyObject *Py_BuildValue(char *format, …)
    该函数可以和PyArg_ParseTuple()函数一样识别一系列的格式串,但是输入参数只能是值,而不能是指针。它返回一个Python对象。
    和PyArg_ParseTuple()不同的一点是PyArg_ParseTuple()函数它的第一个参数为元组,Py_BuildValue()则不一定会生成一个元组。它生成一个元组仅仅当格式串包含两个或者多个格式单元,如果格式串为空,返回NONE。
    在下面的描述中,括号中的项是格式单元返回的Python对象类型,方括号中的项为传递的C的值的类型。
    “s” (string) [char *] :将C字符串转换成Python对象,如果C字符串为空,返回NONE。
    “s#” (string) [char *, int] :将C字符串和它的长度转换成Python对象,如果C字符串为空指针,长度忽略,返回NONE。
    “z” (string or None) [char *] :作用同"s"。
    “z#” (string or None) [char *, int] :作用同"s#“。
    “i” (integer) [int] :将一个C类型的int转换成Python int对象。
    “b” (integer) [char] :作用同"i”。
    “h” (integer) [short int] :作用同"i"。
    “l” (integer) [long int] :将C类型的long转换成Pyhon中的int对象。
    “c” (string of length 1) [char] :将C类型的char转换成长度为1的Python字符串对象。
    “d” (float) [double] :将C类型的double转换成python中的浮点型对象。
    “f” (float) [float] :作用同"d"。
    “O&” (object) [converter, anything] :将任何数据类型通过转换函数转换成Python对象,这些数据作为转换函数的参数被调用并且返回一个新的Python对象,如果发生错误返回NULL。
    “(items)” (tuple) [matching-items] :将一系列的C值转换成Python元组。
    “[items]” (list) [matching-items] :将一系列的C值转换成Python列表。
    “{items}” (dictionary) [matching-items] :将一系类的C值转换成Python的字典,每一对连续的C值将转换成一个键值对。

    2.3 调用Python类

    C++调用python类流程:

    1. 初始化python接口(Py_Initialize)
    2. 初始化python系统文件路径(PyRun_SimpleString)
    3. 调用python文件名(PyImport_ImportModule)
    4. 获取类(PyObject_GetAttrString)
    5. 根据类构造函数实例化对象(PyEval_CallObject)
    6. 获取实例的函数对象(PyObject_GetAttrString)
    7. 传递参数(PyTuple_New,Py_BuildValue)
    8. 调用函数对象(PyObject_CallObject)
    9. 接收函数返回值(PyArg_Parse)
    10. 结束python接口初始化(Py_Finalize)

    在call_python.py里面添加代码:

    class Person:
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def foo(self):
            print(f"my name is {self.name}, my age is {self.age}")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后在main.cpp里面进行调用:

    #include 
    #include 
    
    int main()
    {
        // 1、初始化python接口
        Py_Initialize();
        if (!Py_IsInitialized())
        {
            std::cout << "python init failed" << std::endl;
            return 1;
        }
    
        // 2、初始化python系统文件路径,保证可以访问到 .py文件
        PyRun_SimpleString("import sys");
        PyRun_SimpleString("sys.path.append('./script')");
    
        // 3、调用python文件名,不用写后缀
        PyObject* module = PyImport_ImportModule("call_python");
        if (module == nullptr)
        {
            std::cout << "module not found: call_python" << std::endl;
            return 1;
        }
        // 4、获取类
        PyObject* cls = PyObject_GetAttrString(module, "Person");
        if (!cls)
        {
            std::cout << "class not found: Person" << std::endl;
            return 1;
        }
    
        // 5、给类构造函数传递参数
        // 函数调用的参数传递均是以元组的形式打包的, 2表示参数个数
        // 如果函数中只有一个参数时,写1就可以了
        PyObject* args = PyTuple_New(2);
    
        // 0:第一个参数,传入 int 类型的值 1
        PyTuple_SetItem(args, 0, Py_BuildValue("s", "jack"));
        // 1:第二个参数,传入 int 类型的值 2
        PyTuple_SetItem(args, 1, Py_BuildValue("i", 18));
    
        // 6、根据类名实例化对象
        PyObject* obj = PyObject_CallObject(cls, args);
    
        // 7、根据对象得到成员函数
        PyObject* func = PyObject_GetAttrString(obj, "foo");
        if (!func || !PyCallable_Check(func))
        {
            std::cout << "function not found: foo" << std::endl;
            return 1;
        }
    
        // 8、使用C++的python接口调用该函数
        PyObject_CallObject(func, nullptr);
    
        // 9、结束python接口初始化
        Py_Finalize();
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
  • 相关阅读:
    Web前端:Web开发人员的顶级前端开发趋势
    处理前端富文本渲染图片自适应(css方法)
    巧用 API 网关构建大型应用体系架构
    基于Yolov8的中国交通标志(CCTSDB)识别检测系统
    评估机器学习模型-摘抄
    Web开发:Web开发中的域概念整理与解读
    资深java面试题及答案整理(五)
    HTML爱心代码 | 一起体验理工男的极致浪漫(电视剧男主同款)
    【数据结构】冒泡排序,快速排序的学习知识总结
    leetcode(1)链表
  • 原文地址:https://blog.csdn.net/matt45m/article/details/132761794