环境配置

以 Python38 为例,把 Python 的 includelib 拷贝到自己的工程目录下:

1
2
C:\Users\Administrator\AppData\Local\Programs\Python\Python38\include
C:\Users\Administrator\AppData\Local\Programs\Python\Python38\libs

Python38 是 64 位的(x64),Python38-32 是 32 位的(x86)

注意是 libs 而不是 Lib

然后在工程中包括进去:

Visual Studio 项目属性 - 链接器 - 常规 - 附加库目录:libs

测试发现调用 Python 时项目只支持 x64 Release

错误解决

Debug 调用 Release lib

Debug 模式运行调用的是带 _d后缀的库,无法找到。

include/config.h 中将 Python38_d.lib 改成 Python38.lib

或者把 libs 里面的 Python38.lib 改成 Python38_d.lib

无法解析的外部符号 __imp__Py_Initialize

这是由于调用的 Python 版本与活动解决方案平台的版本不一致导致的。

平台 x86(默认)对应 Python38_32 文件夹,x64 对应 Python38(默认64)文件夹。

应用程序无法正常启动(0xc000007b)

通常是 32 位的目标平台用了 64 位的 lib,或者反过来。

检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项: 值“0”不匹配值“2”

当前工程是 Debug 版本,而引用的库文件是 Release 版本,只需要把当前的 Debug 模式改成 Release 模式就可以了。

同样,如果检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项: 值“2”不匹配值“0”,则说明是 Release 模式引用了 Debug 的库文件

检测到“RuntimeLibrary”的不匹配项: 值“MT_StaticRelease”不匹配值“MD_DynamicRelease”

项目右键 - 属性 - C/C++ - 代码生成 - 运行库,改成(Release 为 MT,Debug 为 MTD)

例子

Python:hello.py (文件名就是导入的模块名)

1
2
3
4
5
6
def Hello():
print("Hello World")

def _add(x, y):
print('add', x, 'and', y)
return x + y

C++:main.cpp

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
61
62
63
64
#include <stdlib.h>
#include <iostream>

#include "include\Python.h"

using namespace std;

int main(int argc, char* argv[])
{
// 初始化 Python 环境
Py_Initialize();

PyRun_SimpleString("import sys");
// 添加 Python 模块路径
// PyRun_SimpleString(chdir_cmd.c_str());
PyRun_SimpleString("sys.path.append('./')");

// 导入 Python 的 Hello 模块(文件名)
PyObject* pModule = PyImport_ImportModule("hello");
if (!pModule)
{
cout << "Python get module failed." << endl;
return -1;
}
cout << "Python get module succeed." << endl;

// 获取并调用 Python 的 Hello 函数(无参无返,直接调用)
PyObject * pFunc = NULL;
pFunc = PyObject_GetAttrString(pModule, "Hello");
PyEval_CallObject(pFunc, NULL);

// 获取 Python 的 _add 函数
PyObject* pv = PyObject_GetAttrString(pModule, "_add");
if (!pv || !PyCallable_Check(pv))
{
cout << "Can't find funftion (_add)" << endl;
return -1;
}
cout << "Get function (_add) succeed." << endl;

// 初始化要传入的参数,args 配置成传入两个参数的模式
PyObject* args = PyTuple_New(2);
// 将 Long 型数据转换成 Python 可接收的类型
PyObject* arg1 = PyLong_FromLong(4);
PyObject* arg2 = PyLong_FromLong(3);

// 将 arg1 配置为 arg 带入的第一个参数
PyTuple_SetItem(args, 0, arg1);
// 将 arg1 配置为 arg 带入的第二个参数
PyTuple_SetItem(args, 1, arg2);

// 传入参数调用函数,并获取返回值
PyObject* pRet = PyObject_CallObject(pv, args);

if (pRet)
{
// 将返回值转换成long型
long result = PyLong_AsLong(pRet);
cout << "result:" << result << endl;
}

Py_Finalize();
return 0;
}

函数传参

1
2
3
4
pFunc = PyObject_GetAttrString(pModule, "add");   // Python 文件中的函数名  
PyObject *pArgs = PyTuple_New(2); // 函数调用的参数传递均是以元组的形式打包的,2表示参数个数
PyTuple_SetItem(pArgs, 0, Py_BuildValue("i", 5)); // 传 int 类型参数
PyTuple_SetItem(pArgs, 1, Py_BuildValue("i", 7));

返回值

1
2
PyObject* pReturn = PyObject_CallObject(pv, args);
PyArg_Parse(pReturn, "i", &result); // 转换为 int 类型

传递数组

1
2
3
4
5
6
7
8
9
10
11
12
double array[] = { 1.2, 4.5, 6.7, 8.9, 1.5, 0.5 };

PyObject* pList = PyList_New(6);
for (int i = 0; i < PyList_Size(pList); i++)
{
PyList_SetItem(pList, i, PyFloat_FromDouble(array[i]));
}

PyObject* pArgs = PyTuple_New(1);
PyTuple_SetItem(pArgs, 0, pList);

PyObject* pReturn = PyEval_CallObject(pFunc, pArgs);

Python代码处理

在发布软件的时候,通常我们都不希望代码可以直接被别人看到。

以上的 exe 要想能够单独运行,必须把 Python 脚本拷过去。为了不让别人能直接看到源码,可以拷过去生成的 .pyc 文件。