记录一下各种语言之间进行简单的相互调用的做法。
Cpp 调用 Python
在C++程序中调用Python解释器,包括执行简单Python语句(字符串形式),以及执行整个py脚本(文件形式)。
最简单的例子:调用Python解释器,输出HelloWorld,C++源文件如下
main.cpp1 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.py1 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.cpp1 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.cpp1 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")
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.h1 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.cpp1 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.m1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| loadlibrary('demo.dll','demo.h');
libisloaded('demo');
libfunctions('demo');
calllib('demo', 'multiply', 5.20, 13.14) calllib('demo', 'add', 5, 13)
unloadlibrary('demo')
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.py1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import matlab.engine
eng = matlab.engine.start_matlab()
result = eng.sqrt(2.0) print(result)
eng.eval("disp('Hello, world! (from MATLAB)')", nargout=0)
eng.quit()
|
如果需要调用自己写的 MATLAB 函数,可以先将函数放在当前目录或将所在目录加入 MATLAB 搜索路径,例如考虑下面的自定义 MATLAB 函数:
myadd.m1 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)
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)
eng.quit()
|
这种方式适合在 Python 程序或 Jupyter Notebook 中复用已有 MATLAB 代码,或者调用 MATLAB 中的特定工具箱函数。
MATLAB 调用 Python
在 MATLAB 中也可以直接调用 Python。
参考官方文档:从 MATLAB 中调用 Python。
首先需要确认 MATLAB 使用的是哪个 Python 解释器:
如果需要指定 Python 路径,可以使用:
1
| pyenv("Version", "path/to/python")
|
可以在 MATLAB 中调用简单的 Python 函数:
test_python.m1 2 3 4 5 6 7 8
| py.print("Hello, world! (from Python)");
math = py.importlib.import_module("math"); result = math.sqrt(2.0);
disp(result);
|
也可以调用自己写的 Python 脚本。假设有一个 demo.py 文件:
demo.py1 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
| 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 之间并不总是自动转换,数据类型的转换仍然是跨语言调用的检查重点。