系统环境:
Windows10
Python3.6
Visual Studio 2017
在C/C++中调用Python,网上很多文章都有简单的介绍,但是很多都不全面或已失效。本文将我自己在配置过程中的主要流程和遇到的问题记录下来,以供参考。
检查系统的环境变量中是否已有PYTHONHOME一项,如果缺失则需要补上,否则会导致之后在C程序中调用Py_Initialize()时遇到Python初始化错误:
PYTHONHOME项的值为系统中Python所在的根目录,以本人系统中的环境变量为例:
或:可以在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字符串,需要经过转换。
在visual studio中配置Python,需要与Python的系统版本相对应,比如64位的Python以vs中x86的配置来调试,也是会报错的,必须用x64的配置才能正确运行。
查看Python是64位还是32位:
在上图中的位置,64位会显示64 bit,32位会显示32 bit。本人系统中的Python是64位的,以下以64位为例。
主要是把包含目录和库目录地址加入到项目中。
包含目录就是Python根目录下的include文件夹,Python.h
和其它必需的头文件就在该文件夹下。
库目录是Python根目录下的libs文件夹,是python36.lib
等所在的文件夹。如果该文件夹下没有python36_d.lib
,可复制python36.lib
并重命名为python36_d.lib
备用。
或:包含目录和库目录也可以直接在下图所示的位置添加,与上文中效果是一致的(细节上的区别可参考该文章)。
接下来添加依赖项,这里release和debug的配置是不一样的,需要分别配置。
上面的python36.lib和python36_d.lib已经在3.2节提到过。
*实际上,依赖项添加与否好像没什么用,实测不添加依赖项,程序仍然可以成功运行。不过保险起见还是加上这一步。
测试用代码来自该博客。原博客的代码是基于Python2,这里都改为了基于Python3的版本。
将以下代码保存在一个文件中,命名为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()
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文件所在目录。
上面调用python类的部分其实是有问题的,实例化的时候python类的构造函数并没有被真正调用,对类中各方法的操作本质上与普通函数无异,当遇到类中成员变量的传递就会遇到问题。
正确的python类调用方法可以参考这篇博客,主要流程如下:
// 得到类的构造函数
PyObject* pConstruct = PyInstanceMethod_New(pClass);
// 调用构造函数使其实例化
PyObject* pIns = PyObject_CallObject(pConstruct, nullptr); // 需传参时将nullptr替换为参数
// 调用类的方法
PyObject_CallMethod(pIns,"some method", nullptr); // 需传参时将nullptr替换为参数
[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