C/C++调用Python3 | VS2017配置流程

系统环境:
	Windows10
	Python3.6
	Visual Studio 2017

在C/C++中调用Python,网上很多文章都有简单的介绍,但是很多都不全面或已失效。本文将我自己在配置过程中的主要流程和遇到的问题记录下来,以供参考。

1、PYTHONHOME

检查系统的环境变量中是否已有PYTHONHOME一项,如果缺失则需要补上,否则会导致之后在C程序中调用Py_Initialize()时遇到Python初始化错误:
C/C++调用Python3 | VS2017配置流程_第1张图片
PYTHONHOME项的值为系统中Python所在的根目录,以本人系统中的环境变量为例:
PYTHONHOME示例
:可以在Py_Initialize()之前通过Py_SetPythonHome()函数来设定Python路径,如下:

wchar_t* c2w(const char *c)
{
    const size_t cSize = strlen(c) + 1;
    wchar_t* wc = new wchar_t[cSize];
    mbstowcs(wc, c, cSize);
    return wc;
}

Py_SetPythonHome(c2w(“my/python/home”));
Py_Initialize();

还要注意Py_SetPythonHome()的参数是wchar_t类型的,不能直接传char字符串,需要经过转换。

2、64位 v.s. 32位

在visual studio中配置Python,需要与Python的系统版本相对应,比如64位的Python以vs中x86的配置来调试,也是会报错的,必须用x64的配置才能正确运行。
C/C++调用Python3 | VS2017配置流程_第2张图片
查看Python是64位还是32位:
C/C++调用Python3 | VS2017配置流程_第3张图片
在上图中的位置,64位会显示64 bit,32位会显示32 bit。本人系统中的Python是64位的,以下以64位为例。

3、visual studio中的属性设置

主要是把包含目录和库目录地址加入到项目中。

3.1 包含目录

包含目录就是Python根目录下的include文件夹,Python.h和其它必需的头文件就在该文件夹下。
C/C++调用Python3 | VS2017配置流程_第4张图片

3.2 库目录

库目录是Python根目录下的libs文件夹,是python36.lib等所在的文件夹。如果该文件夹下没有python36_d.lib,可复制python36.lib并重命名为python36_d.lib备用。
C/C++调用Python3 | VS2017配置流程_第5张图片
:包含目录和库目录也可以直接在下图所示的位置添加,与上文中效果是一致的(细节上的区别可参考该文章)。
C/C++调用Python3 | VS2017配置流程_第6张图片

3.3 添加依赖项

接下来添加依赖项,这里release和debug的配置是不一样的,需要分别配置。

3.3.1 release配置

C/C++调用Python3 | VS2017配置流程_第7张图片

3.3.2 debug配置

C/C++调用Python3 | VS2017配置流程_第8张图片
上面的python36.lib和python36_d.lib已经在3.2节提到过。
*实际上,依赖项添加与否好像没什么用,实测不添加依赖项,程序仍然可以成功运行。不过保险起见还是加上这一步。

4、代码测试

测试用代码来自该博客。原博客的代码是基于Python2,这里都改为了基于Python3的版本。

4.1 Python代码

将以下代码保存在一个文件中,命名为pytest.py

#test function: pytest.py
def add(a,b):  
    print("in python function add")
    print("a = " + str(a))
    # print("b = " + str(b))
    # print("ret = " + str(a+b))
    # return  
  # 
def foo(a):  
  
    print("in python function foo")
    print("a = " + str(a))
    # print("ret = " + str(a * a))
    # return   
  # 
class guestlist:  
    def __init__(self):  
        print("aaaa")
    def p():  
      print("bbbbb")
    def __getitem__(self, id):  
      return "ccccc"  
def update():  
    guest = guestlist()  
    print(guest['aa'])
  
update()
4.2 C++代码

visual studio中新建一个c++空项目,新建一个源文件,导入以下代码:

#include   

int main(int argc, char** argv)
{
	// 初始化Python  
	//在使用Python系统前,必须使用Py_Initialize对其  
	//进行初始化。它会载入Python的内建模块并添加系统路  
	//径到模块搜索路径中。这个函数没有返回值,检查系统  
	//是否初始化成功需要使用Py_IsInitialized。  
	Py_Initialize();

	// 检查初始化是否成功  
	if (!Py_IsInitialized()) {
		return -1;
	}
	// 添加当前路径  
	//把输入的字符串作为Python代码直接运行,返回0  
	//表示成功,-1表示有错。大多时候错误都是因为字符串  
	//中有语法错误。  
	PyRun_SimpleString("import sys");
	PyRun_SimpleString("print('---import sys---')");
	PyRun_SimpleString("sys.path.append('./')");

	// 输出当前目录
	PyRun_SimpleString("import os");
	PyRun_SimpleString("print('pwd:', os.getcwd())");

	PyRun_SimpleString("import pytest");

	PyObject *pName, *pModule, *pDict, *pFunc, *pArgs;

	// 载入名为pytest的脚本  
	pName = PyUnicode_FromString("pytest");  // Python2中为PyString_FromString()
	pModule = PyImport_Import(pName);
	if (!pModule) {
		printf("can't find pytest.py");
		getchar();
		return -1;
	}
	pDict = PyModule_GetDict(pModule);
	if (!pDict) {
		return -1;
	}

	// 找出函数名为add的函数  
	printf("----------------------\n");
	pFunc = PyDict_GetItemString(pDict, "add");
	if (!pFunc || !PyCallable_Check(pFunc)) {
		printf("can't find function [add]");
		getchar();
		return -1;
	}

	// 参数进栈  
	*pArgs;
	pArgs = PyTuple_New(2);

	//  PyObject* Py_BuildValue(char *format, ...)  
	//  把C++的变量转换成一个Python对象。当需要从  
	//  C++传递变量到Python时,就会使用这个函数。此函数  
	//  有点类似C的printf,但格式不同。常用的格式有  
	//  s 表示字符串,  
	//  i 表示整型变量,  
	//  f 表示浮点数,  
	//  O 表示一个Python对象。  

	PyTuple_SetItem(pArgs, 0, Py_BuildValue("l", 3));
	PyTuple_SetItem(pArgs, 1, Py_BuildValue("l", 4));

	// 调用Python函数  
	PyObject_CallObject(pFunc, pArgs);

	//下面这段是查找函数foo 并执行foo  
	printf("----------------------\n");
	pFunc = PyDict_GetItemString(pDict, "foo");
	if (!pFunc || !PyCallable_Check(pFunc)) {
		printf("can't find function [foo]");
		getchar();
		return -1;
	}

	pArgs = PyTuple_New(1);
	PyTuple_SetItem(pArgs, 0, Py_BuildValue("l", 2));

	PyObject_CallObject(pFunc, pArgs);

	printf("----------------------\n");
	pFunc = PyDict_GetItemString(pDict, "update");
	if (!pFunc || !PyCallable_Check(pFunc)) {
		printf("can't find function [update]");
		getchar();
		return -1;
	}
	pArgs = PyTuple_New(0);
	PyTuple_SetItem(pArgs, 0, Py_BuildValue(""));
	PyObject_CallObject(pFunc, pArgs);

	Py_DECREF(pName);
	Py_DECREF(pArgs);
	Py_DECREF(pModule);

	// 关闭Python  
	Py_Finalize();
	system("pause");
	return 0;
}

需要注意的是,Python2中的PyString_FromString(),到了Python3中应修改为PyUnicode_FromString(),而不是网上一些资料中提到的PyBytes_FromString()。如上面程序中33行的pName = PyUnicode_FromString("pytest");

在visual studio中调试时,将pytest.py放到.cpp文件所在目录即可。如果使用生成的.exe文件,需要将pytest.py放到.exe文件所在目录。

成功运行的界面如下图所示:
C/C++调用Python3 | VS2017配置流程_第9张图片


补充:

上面调用python类的部分其实是有问题的,实例化的时候python类的构造函数并没有被真正调用,对类中各方法的操作本质上与普通函数无异,当遇到类中成员变量的传递就会遇到问题。
正确的python类调用方法可以参考这篇博客,主要流程如下:

// 得到类的构造函数
PyObject* pConstruct = PyInstanceMethod_New(pClass);
// 调用构造函数使其实例化
PyObject* pIns = PyObject_CallObject(pConstruct, nullptr);  // 需传参时将nullptr替换为参数
// 调用类的方法
PyObject_CallMethod(pIns,"some method", nullptr);  // 需传参时将nullptr替换为参数

5、一些参考网站

[1]https://www.cnblogs.com/yanzi-meng/p/8066944.html
[2]https://blog.csdn.net/hnlylyb/article/details/89498651
[3]https://docs.python.org/3.6/c-api/
[4]https://docs.python.org/3/extending/embedding.html#pure-embedding

你可能感兴趣的:(C/C++调用Python3 | VS2017配置流程)