XingPiaoLiang's

Back

前言#

因为接到了一个实验室的委托,需要将其实验室的一个石油工程上的计算代码模块封装成一个可视化的展示界面,通过输入参数、导入表格数据、后台进行计算输出数据、根据数据进行输出图像。这样一个简单的,不需要太多含金量的小项目,却卡在了第一步: 因为代码过于机密,实验室负责老师不允许将 MATLAB 源代码交给我,所有就有了以下的折腾故事。

疑惑#

首先,遇见这样的情况我首先想到的就是,如果不给我源代码,我怎么才能将其转为 Python 代码,然后用 Python 代码计算得出结果数据,将结果数据传递给前端,前端使用 echarts 进行展示呢?。这是我首先的想法,当然没有了源代码这个方案首先就被否决了。 查阅资料( ChatGPT…)…之后发现,MATLAB 官方是提供了供外部语言进行调用的接口,目前由我的经历来说可以分为两类。

  1. 使用MATLAB.engine调用 MATLAB 的原生函数(需要本地有源代码
  2. 将 MATLAB 源代码进行打包,可以打包成各个语言的扩展程序(这里选择的是打包为 Python Package,使用 Python 进行调用),之后使用相应的语言对 MATLAB 函数进行调用。(**需要本地有相应版本的 MATLAB Runtime **)

在这整个的折腾过程中,一个绕不掉的话题就是版本问题,无论是下文要提到的MATLAB编译代码所需要的编译器(MATLAB Compiler),还是调用端调用打包代码所需要的运行环境(MATLAB Runtime)版本对应是最关键的!! MATLAB2017b编译器打包出来的代码,MATLAB2024b的Runtime无法正常运行。

MATLAB 打包代码#

打包代码,本质上也就是对 MATLAB 源代码进行编译,自然而然地就需要下载 MATLAB 自己的编译器(MATLAB Compiler),接着进行编译。 这里选择打包成供Python进行调用的Python Package,以mainfunc函数为例,打包过后的文件目录大概是这样:

.
├── PackagingLog.html
├── for_redistribution
│   └── MyAppInstaller_web.exe
├── for_redistribution_files_only
│   ├── default_icon.ico
│   ├── mainfunc
│   │   ├── __init__.py
│   │   └── mainfunc.ctf
│   ├── mainfunc.dll
│   ├── mainfuncNative.dll
│   ├── mainfunc_overview.html
│   ├── readme.txt
│   └── setup.py
└── for_testing
    ├── Class1.cs
    ├── Class1Native.cs
    ├── mainfunc
    │   ├── __init__.py
    │   └── mainfunc.ctf
    ├── mainfunc.dll
    ├── mainfunc.xml
    ├── mainfuncNative.dll
    ├── mainfuncNative.xml
    ├── mainfunc_overview.html
    ├── mccExcludedFiles.log
    ├── readme.txt
    ├── requiredMCRProducts.txt
    └── setup.py
bash

可以看到主要分为三个文件夹:

  • for_redistribution:直接提供一个安装程序,用于在服务器部署使用,一键安装MATLAB应用程序 。
  • for_redistribution_files_only: 用于自定义安装,嵌入到不同的操作系统中,包含了具体的Python包
  • for_testing:开发者调试所用,包含更加详细的打包信息。 一般来说在这个场景下,我们就只需要使用for_redistribution_files_only或者是更加详细的for_testing文件夹下的具体的Python Package,也就是直接使用 Setup.py该脚本,将该包下载到当前Python环境中的Package List中。 readme.txt大致内容如下:
1. Prerequisites for Deployment

Verify that version 9.3 (R2017b) of the MATLAB Runtime is installed.
...
Verify that a Windows version of Python 2.7, 3.4, 3.5, and/or 3.6 is installed.

2. Installing the mainfunc Package

A. Change to the directory that contains the file setup.py and the subdirectory mainfunc.
If you do not have write permissions, copy all its contents to a temporary location and
change to that directory.

B. Execute the command:

    python setup.py install [options]

3. Using the mainfunc Package

The mainfunc package is on your Python path. To import it into a Python script or
session, execute:

    import mainfunc
txt

包含了如何安装该包,如何使用包,以及在使用之前的prerequisites。这里需要注意到的是在第一点中,具体的指出了MATLAB Runtime 以及Python版本的情况。还记得上文提到的版本问题吗?这里就显得尤为重要了。当 MATLAB 和 Python 相逢,首先被放大的就是 Python 的(不太精致)版本问题,具体的 MATLAB 适配 Python 版本情况

Python 调用MATLAB打包好的代码#

首先,确保 Python 的版本和 MATLAB 当前的版本是 Compatible 的。 之后就可以开始快乐的安装 MATLAB 的 Package 了。

python setup.py install
bash

执行之后,你就会在当前 Python 环境下的 Python Package list看到下载好的包了。 接下来一气呵成,直接调用 MATLAB 优美的函数:

# 你的包名
import mainfunc
python

此时你会发现,我完全不知道这个函数的参数签名,应该怎么调啊?所以需要提供一个详细的关于该 MATLAB 函数的参数签名。

% 类似于这种
[Vsumt,Vt]=mainfunc(omegaU,KdU)
matlab

关于参数的类型,越详细越好,因为在之后你会遇到很多不明所以的类型问题。当然,支持使用arguments关键字对MATLAB函数进行参数的详细定义是从MATLAB2019b开始的。

具体调用#

import mainfunc 

# 该函数会返回一个调用这个MATLAB函数的句柄
# 查看源码会发现,该初始化方法做了一系列工作,包括检验runtime环境是否存在符合....
func_handle = mainfunc.initialize()

omegaU = 0
kdU = 1

# 直接开始调用函数
# 注意这里的方法,应该完全对应你的MATLAB函数的方法
Vsumt, Vt = func_handle.mainfunc(omegaU, KdU)
python

这样,差不多是调用的全过程,但你会发现,会报以下错误(也就是我踩的各种坑)

MATLAB 不支持 type int
bash

所以类型问题俨然而生。

类型问题#

MATLAB原本是不支持 Python 的原生类型的,所以放入函数中的参数也一定不能是 Python 的数据类型。此时就需要引入一个用于 MATLAB为 Python 专门提供的一个 SDK(MATLAB Engine API for Python)。 安装方法:在 MATLAB 命令行输入以下命令:

>> cd (fullfile(matlabroot, 'extern', 'engines', 'python'))
>> system('python setup.py install')
bash

注意:这里必须是给当前调用 MATLAB 函数的 Python 环境进行安装该包,也就是说如果使用Conda或者其他的环境管理工具,需要切换到之前的 Python 环境,此处默认使用的是系统环境中的 Python。可以直接将python setup.py install中的python改成环境中绝对路径的python可执行文件如:D:\Users\jay\anaconda3\envs\petrol-env\python.exe setup.py install

安装好之后开始重新调用:

import mainfunc 
import matlab
func_handle = mainfunc.initialize()

# 注意,这里matlab.double必须以list接收参数
omegaU = matlab.double([0])
kdU = matlab.double([0])

Vsumt, Vt = func_handle.mainfunc(omegaU, KdU)
python

输入参数解决了,输出结果也存在着很多问题。

  • 输入参数有类型限制,那么算出来的输出结果的类型也是对应的matlab.double,这样对之后的python原生操作及其不友好。
  • 输出的结果数量限制。 针对第二个问题来说,上面的代码,你会发现运行之后会报类似于can not unpack so many variable的错误,也就是说, mainfunc默认只会返回第一个参数
# 使用该参数解决
Vsumt, Vt = func_handle.mainfunc(omegaU, KdU, nargout=2)
python

针对第一个问题,那就只能是慢慢的调试对类型进行转化了(如果涉及到复杂的矩阵,需要编写合适的函数将matlab.double类型转换成原生 Python 数据类型。

完成调用#

至此,一个简单的调用就完成了。

import mainfunc 
import matlab
func_handle = mainfunc.initialize()

# 注意,这里matlab.double必须以list接收参数
omegaU = matlab.double([0])
kdU = matlab.double([0])

Vsumt, Vt = func_handle.mainfunc(omegaU, KdU, nargout=2)

# 处理数据类型
handle_the_type(Vsumt, Vt)
python

#

  • 查看官方文档,MATLAB 官方是不支持,将 M * N 的 cell 数组在 Python 中进行返回的,只能够返回 1 * N / N * 1 的 cell 数组。(很难受)
  • 在打包matlab函数的时候,打包目标文件时,这个文件中只能有mainfunc这一个函数,并且明确定义。

参考:

如果MATLAB和Python相逢
https://astro-pure.js.org/blog/matlab-python/matlab%E4%B8%8Epython%E7%9B%B8%E9%80%A2
Author erasernoob
Published at April 27, 2025
Comment seems to stuck. Try to refresh?✨