记录一下在Cpp程序和Python脚本中相互调用的方法。

Cpp调用Python

在C++程序中调用Python解释器,包括执行简单Python语句(字符串形式),以及执行整个py脚本(文件形式)。

执行简单命令

最简单的例子:调用Python解释器,输出HelloWorld,C++源文件如下

main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG 1
#else
#include <Python.h>
#endif

int main() {
Py_Initialize();

PyRun_SimpleString("print('Hello, world! (from Python)')");

Py_Finalize();
return 0;
}

注意在导入Python.h时进行了一些处理,因为我们的电脑中通常只有Release版本的Python库,并没有Debug版本。

我们使用CMake完成Python库的导入,主要语句如下

1
2
3
find_package(Python REQUIRED COMPONENTS Interpreter Development)
add_executable(test main.cpp)
target_link_libraries(test PRIVATE Python::Python)

正常编译执行即可。

执行Python脚本

假设我们在path路径下提供了demo.py脚本,例如

demo.py
1
2
3
4
5
6
7
8
9
10
11
12
import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [2, 3, 5, 7, 11]

plt.plot(x, y)
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Sample Plot')
plt.grid(True)

plt.savefig('plot.png')

将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
#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG 1
#else
#include <Python.h>
#endif

int main() {
Py_Initialize();

FILE *file = fopen("path/demo.py", "r");
if (file != nullptr) {
PyRun_SimpleFile(file, "demo.py");
fclose(file);
}
else {
fprintf(stderr, "Failed to open Python script file\n");
return 1;
}

Py_Finalize();

return 0;
}

此时我们就可以让C++程序在执行时自动调用Python解释器执行demo.py脚本。

Python调用Cpp

我们将Cpp程序封装为动态库(保证提供C语言接口),在Python中使用内置的ctypes模块所提供的工具可以进行调用。

例如下面的动态库myfunc.dll

myfunc.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <cstring>

extern "C" {
__declspec(dllexport) int add(int a, int b) { return a + b; }

__declspec(dllexport) void hello_world(char *buffer, int buffer_size) {
const char *message = "Hello, World!";
int message_length = static_cast<int>(strlen(message));
if (message_length < buffer_size) { strcpy(buffer, message); }
else {
strncpy(buffer, message, buffer_size - 1);
buffer[buffer_size - 1] = '\0';
}
}
}

提供两个函数:

  • add函数:返回两个整数的和;
  • hello_world函数:在传入的buffer中填入helloworld字符串。如果直接在C++中输出到控制台,在Python调用时是看不见的;也不能直接返回一个const char*字符串指针或者C++的std::string,都比较麻烦,传入字符串缓冲区是最简便的实现。

在Python中可以使用下面的方式调用这两个接口

1
2
3
4
5
6
7
8
9
10
11
12
import ctypes

mylib = ctypes.CDLL("./bin/myfunc_d.dll") # myfunc.dll or myfunc_d.dll

result = mylib.add(5, 3)
print(f"Result of adding: {result}, type is {type(result)}")

buffer_size = 20
buffer = ctypes.create_string_buffer(buffer_size)
mylib.hello_world(buffer, buffer_size)
hello_world_string = buffer.value.decode("utf-8")
print(hello_world_string)

注意:对于Jupyter Notebook,在加载了对应的动态库之后不会自动卸载,系统会保护正在使用的动态库,如果此时尝试修改动态库则会编译报错。