记录一下各种语言之间进行简单的相互调用的做法。

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)

正常编译执行即可。

假设我们在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,在加载了对应的动态库之后不会自动卸载,系统会保护正在使用的动态库,如果此时尝试修改动态库则会编译报错。

MATLAB 调用 Cpp

首先,我们简单创建一个动态库,由于我们的实验环境是Windows+MSVC,需要注意动态库的符号导出问题,动态库包括头文件demo.h和源文件demo.cpp

demo.h
1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma once

#ifdef MY_EXPORTS
#define MY_API __declspec(dllexport)
#else
#define MY_API __declspec(dllimport)
#endif

extern "C" {
MY_API int add(int a, int b);

MY_API double multiply(double a, double b);
}
demo.cpp
1
2
3
4
5
6
#define MY_EXPORTS
#include "demo.h"

int add(int a, int b) { return a + b; }

double multiply(double a, double b) { return a * b; }

然后在Release模式下编译得到demo.dll,将其和头文件一起放在同一个目录中,使用下面的MATLAB脚本进行测试

test.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% 加载dll
loadlibrary('demo.dll','demo.h');
% 判断dll是否加载
libisloaded('demo');

% 查看dll中的函数
libfunctions('demo');
% 调用dll中的函数
calllib('demo', 'multiply', 5.20, 13.14)
calllib('demo', 'add', 5, 13)

% 卸载dll
unloadlibrary('demo')
% 判断dll是否加载
libisloaded('demo')

运行结果如下

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

Functions in library demo:

add multiply


ans =
68.3280
ans =
18
ans =
logical
0

这里的情况比较简单,在更一般的情况下还存在MATLAB数据类型和C++数据类型的转换问题,暂时不作讨论。

Python 调用 MATLAB

在 Python 中可以通过 MATLAB Engine API 调用 MATLAB。

参考官方文档:从 Python 中调用 MATLAB

这里以 MATLAB R2023b 和 Python 3.11 为例,首先在环境中安装对应版本的 matlabengine 包:

1
pip install matlabengine==23.2.3

然后在 Python 中测试调用 MATLAB:

test_matlab.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matlab.engine

# 启动 MATLAB
eng = matlab.engine.start_matlab()

# 调用 MATLAB 内置函数
result = eng.sqrt(2.0)
print(result)
# 1.4142135623730951

# 执行简单 MATLAB 命令
eng.eval("disp('Hello, world! (from MATLAB)')", nargout=0)
# Hello, world! (from MATLAB)

# 关闭 MATLAB
eng.quit()

如果需要调用自己写的 MATLAB 函数,可以先将函数放在当前目录或将所在目录加入 MATLAB 搜索路径,例如考虑下面的自定义 MATLAB 函数:

myadd.m
1
2
3
function z = myadd(x, y)
z = x + y;
end

在 Python 中调用:

1
2
3
4
5
6
7
8
9
import matlab.engine

eng = matlab.engine.start_matlab()

result = eng.myadd(3.0, 5.0)
print(result)
# 8.0

eng.quit()

需要注意 MATLAB 的启动和查找目录,可以用下面的命令查看和添加路径

1
2
print(eng.pwd())
eng.addpath("path/to/matlab/files", nargout=0)

如果需要传递矩阵,可以使用 matlab.double 进行类型转换:

1
2
3
4
5
6
7
8
9
10
11
12
import matlab
import matlab.engine

eng = matlab.engine.start_matlab()

A = matlab.double([[1, 2, 3], [4, 5, 6]])
B = eng.transpose(A)

print(B)
# [[1.0,4.0],[2.0,5.0],[3.0,6.0]]

eng.quit()

这种方式适合在 Python 程序或 Jupyter Notebook 中复用已有 MATLAB 代码,或者调用 MATLAB 中的特定工具箱函数。

MATLAB 调用 Python

在 MATLAB 中也可以直接调用 Python。

参考官方文档:从 MATLAB 中调用 Python

首先需要确认 MATLAB 使用的是哪个 Python 解释器:

1
pyenv

如果需要指定 Python 路径,可以使用:

1
pyenv("Version", "path/to/python")

可以在 MATLAB 中调用简单的 Python 函数:

test_python.m
1
2
3
4
5
6
7
8
% 调用 Python 内置函数
py.print("Hello, world! (from Python)");

% 调用 Python math 模块
math = py.importlib.import_module("math");
result = math.sqrt(2.0);

disp(result);

也可以调用自己写的 Python 脚本。假设有一个 demo.py 文件:

demo.py
1
2
3
4
5
def add(a, b):
return a + b

def hello():
return "Hello, world!"

在 MATLAB 中调用:

1
2
3
4
5
6
7
8
9
10
11
12
% 将 Python 文件所在目录加入 Python 搜索路径
if count(py.sys.path, "path/to/python/files") == 0
insert(py.sys.path, int32(0), "path/to/python/files");
end

demo = py.importlib.import_module("demo");

result = demo.add(3, 5);
disp(result);

message = demo.hello();
disp(message);

注意这里 demo.hello 会获取函数对象,demo.hello() 才是函数或方法的无参数调用,这与 Python 的语法逻辑一致,但是与 MATLAB 中常见的语法逻辑不同。

如果修改了 Python 文件,MATLAB 中已经导入的模块不会自动刷新,可以使用:

1
demo = py.importlib.reload(demo);

对于数组数据,可以在 MATLAB 和 Python 之间进行转换。例如在 MATLAB 中调用 NumPy:

1
2
3
4
5
6
np = py.importlib.import_module("numpy");

a = np.array([1, 2, 3, 4]);
b = np.sum(a);

disp(b);

需要注意的是,MATLAB 数组、Python list、NumPy array 之间并不总是自动转换,数据类型的转换仍然是跨语言调用的检查重点。