# 使用 Python 调用 Simulink 进行仿真

## 概述

由于工作上的一些需要，需要将 Simulink 部署到服务器上，通过 Restful API 调用，传入参数进行仿真。

Simulink 是 [Matlab](https://www.mathworks.com/products/matlab.html) 的一个模块，属于公司 MathWorks，是一款商业软件。服务的部署有多种方式可选，但是选择的方法不恰当，可能会面临授权和收费的问题。MathWorks 的收费向来很多，显然，通过付费来解决问题并不是我们想要的。

## 部署方式

Matlab 主程序提供了一些部署方法，这些部署方法更加适合对`.m`文件进行部署。

- 「代码生成」中的 Matlab Coder 可以将 Matlab 代码编译成原生的 C 语言程序，从而不依赖 Matlab 环境运行，但是其不支持 Simulink 的编译
- 「应用程序部署」那一类，都需要 Matlab 运行环境，其实现的是使用对应编程语言调用 Matlab 运行环境的接口。

![](https://img.papergate.top:5000/i/2025/09/68d4af57d6051.webp)

Simulink 子程序中也提供了一些部署方法，这些部署方法是专门给 Simulink 适配的。

![](https://img.papergate.top:5000/i/2025/09/68d4b260dd9a0.webp)

其中的 Simulink Coder 可以将 Simulink 模块编译成原生的 C语言程序，从而不依赖 Matlab 环境运行。

### Matlab Production/Web App Server

首先，MathWorks 公司肯定是料想到了它的客户需要将 Matlab 相关服务部署到服务器上运行，于是，它推出了两款产品。

分别是：[Matlab Production Server](https://www.mathworks.com/products/matlab-production-server.html) 和 [Matlab Web App Server](https://www.mathworks.com/products/matlab-web-app-server.html)，这两款产品的区别在于，Matlab Production Server 提供的是 Restful API，而 Matlab Web App Server 可以通过可视化的方式在浏览器上运行 Matlab 应用程序。

> [!info]  信息
> 这两款软件都是商业软件，所以都是要收费的。

编译器的位置如下：

![](https://img.papergate.top:5000/i/2025/09/68d389225bed7.webp)

#### Matlab Production Server

Matlab Production Server 可以直接将 `.m`文件及其他关联附属文件打包。

![](https://img.papergate.top:5000/i/2025/09/68d38a017dd29.webp)

打包的过程中使用依赖关系分析器，并将依赖关系用图列举出来：

![](https://img.papergate.top:5000/i/2025/09/68d38b0e83219.webp)

最终，相关的依赖模块会以各种`Addon`的形式体现在`buildresult.json`文件中：

```json
{  
    "RuntimeDependencies": [  
       {"ID":35003, "DisplayName":"MATLAB Standard Runtime Addon", "Optional":"no"},  
       {"ID":35010, "DisplayName":"MATLAB Base Runtime", "Optional":"no"},  
       {"ID":35054, "DisplayName":"MATLAB Production Server Runtime Addon", "Optional":"no"},  
       {"ID":35274, "DisplayName":"Simulink Compiler Runtime Addon", "Optional":"no"}  
    ]  
}
```

于是在执行相应函数时，只会加载对应的 Addon。

#### Matlab Web App Server

Matlab Web App Server 不同于 Matlab Production Server，它需要先创建一个 Matlab 的 App 应用，并在其中设计好相应的前端 UI 并绑定函数功能，如下：

![](https://img.papergate.top:5000/i/2025/09/68d38d04e944c.webp)

![](https://img.papergate.top:5000/i/2025/09/68d38cd0831db.webp)

由于本人并不打算使用这种方式构建，因而不再进行进一步的功能探索。

### MATLAB Engine API

MATLAB Engine API 是 MathWorks 提供的官方工具，允许在 Python 中调用 MATLAB 函数、脚本和数据。

找到如下路径：`[MATLAB安装路径]/extern/engines/python`

![](https://img.papergate.top:5000/i/2025/09/68d38815a727e.webp)

使用命令安装：

```shell
python setup.py install
```

MATLAB Engine API 就相当于在后台打开了一个 matlab 程序，使用 python 执行的命令会**原封不动**的转变成执行 matlab 命令：

```python
# 导入库
import matlab.engine
# 在后台打开 matlab 程序
eng = matlab.engine.start_matlab()
# 添加 matlab 项目的目录
eng.addpath('path')
# 执行目录下的 xxx.m 文件定义的函数
result = eng.xxx(1,2)
```

这种方式只是在 matlab 外套了一层 python 的包装，怎样使用 matlab 就怎样使用 python，不会出现任何的意料之外的报错。

也正因为这样，这种方式需要在服务器上安装**完整的 matlab 程序**并授权，并且每次服务启动前需要先启动 matlab，执行 simulink 仿真前也会加载相应模块，每次运行前会有**好几秒**的加载时间，需要进行优化并预先加载资源，所以这种方式不适合部署在服务器上。

### Python 包编译器

![](https://img.papergate.top:5000/i/2025/09/68d394cb51102.webp)

Python 包编译器可以直接将 `.m`文件及其他关联附属文件打包，如下：

![](https://img.papergate.top:5000/i/2025/09/68d3954bd5389.webp)

安装一下即可：

```shell
python setup.py install
```

```python
# 导入包
import RPC_ABSmin  

# 初始化包
handle = RPC_ABSmin.initialize()
# 执行包中定义的函数
result = handle.run_RPC_ABSmin(1,2)
```

这种方式不需要完整安装 matlab 应用，只需要安装 matlab 运行环境，执行函数时只加载**依赖的模块**，所以速度上是要快上不少的，并且 matlab 的运行环境是**不收费且无需授权**的，所以这是一种可以考虑的部署方式。

> [!warning]  警告
> 不过，本人在使用这种方式运行 Simulink 仿真的时候遇到了未知的错误，执行`sim`函数时会报错，因而最终也没有采用这种方式。

### Simulink Coder

使用 Simulink Coder 前需要安装一款合适的 C 编译器，如下：

![](https://img.papergate.top:5000/i/2025/09/68d4b9f4316ac.webp)

在「APP」中选择 Simulink Coder，然后在弹出的 C 代码标签页上点击「编译」即可：

![](https://img.papergate.top:5000/i/2025/09/68d4babb288a7.webp)

这样，便会生成如下的 C Make 项目：

![](https://img.papergate.top:5000/i/2025/09/68d4bbab1ecf8.webp)

打开文件夹`cmake-build-debug`，可以看到，matlab 已经为我们编译好了可执行文件。

> [!info]  信息
> 这种方式不依赖于 Matlab 运行环境，并且因为是原生的 C 语言环境，程序的运行速度也很快，所以最后本人便采用这种方式来编译 Simulink。

## 编译共享链接库

使用 Simulink Coder 编译时，默认生成的是可执行文件，这有一定的局限性。

- 一方面，执行可执行文件时，不好指定输入，输出也不好获取，对于 Simulink 这样依赖输入输出的仿真应用，非常不方便。
- 另一方面，可执行文件的中间运算过程是固定的，不方便在仿真的中途修改数据。

所以，需要修改C Make的编译方式，生成共享链接库（Windows 中是 dll 文件，Mac 中是dylib 文件，linux 中是 so 文件）。

找到`CMakeLists.txt`文件，其中有一行用于生成可执行文件：

```shell
add_executable(「xxx」  ${MATLAB_ROOT}/rtw/c/src/common/rt_main.c)
```

将其替换成以下内容：

```shell
# 将原来的add_executable改为add_library并指定为SHARED库  
add_library(「xxx」 SHARED ${MATLAB_ROOT}/rtw/c/src/common/rt_main.c)  
  
# 设置库的属性  
set_target_properties(「xxx」 PROPERTIES PREFIX ""  
        BUILD_WITH_INSTALL_RPATH TRUE  
        INSTALL_RPATH_USE_LINK_PATH TRUE  
        SOVERSION 1)  # 添加版本号  
  
# 修改安装规则，将RUNTIME改为LIBRARY  
install(TARGETS 「xxx」  
        LIBRARY DESTINATION "lib"  # 修改为LIBRARY并指定安装目录  
        ARCHIVE DESTINATION "lib"  # 添加静态库安装目录  
        RUNTIME DESTINATION "bin"  # 可执行文件目录(如果有的话)  
        OPTIONAL)
```

重新编译即可获得共享链接库。

## 查看 C 代码

因为后续使用 Python 调用共享链接库时，需要执行库中的 C 函数，因而需要了解源码中 C 函数的变量名。

在`xxx.h`里面，定义了模型的输入和输出，如下：

```c
typedef struct {  
  real_T CV11;                         /* '<Root>/CV11' */  
  real_T CV12;                         /* '<Root>/CV12' */  
  real_T CV21;                         /* '<Root>/CV21' */  
  real_T CV22;                         /* '<Root>/CV22' */  
  boolean_T Reduce1;                   /* '<Root>/Reduce1' */  
  boolean_T Reduce2;                   /* '<Root>/Reduce2' */  
  real_T Power;                        /* '<Root>/Power' */  
} ExtU_RPC_ABSmin_T;
typedef struct {  
  real_T Number;                       /* '<Root>/Number' */  
} ExtY_RPC_ABSmin_T;
```

定义了两个结构体，`ExtU_xxx_T`是输入，`ExtY_xxx_T`是输出。

在`xxx.c`里面，定义了模型的运算过程，如下：

```c
void RPC_ABSmin_initialize(void)  {...}
void RPC_ABSmin_step(void)  {...}
void RPC_ABSmin_terminate(void)  {...}
```

`RPC_ABSmin_initialize`是构造函数，`RPC_ABSmin_step`是迭代函数，而`RPC_ABSmin_terminate`是析构函数。

如果在模型中指定了仿真的步长和时间，就会在构造函数中看到如下内容：

```c
# 步长 0.01 s
RPC_ABSmin_M->Timing.stepSize0 = 0.01;
# 时间 1s
rtmSetTFinal(RPC_ABSmin_M, 1.0);  
```

迭代函数里面是每个步长中需要执行的运算。

> [!info]  信息
> 可见 Simulink 就是定义了初始状态和每一步的运算，然后反复迭代的过程。

## 编写 Python 代码

有了上面 C 代码的铺垫，可以编写 Python 代码如下：

首先，定义模型的输入输出形式，对应`.h`文件：

```python
import ctypes  
  
class ExtU_RPC_ABSmin_T(ctypes.Structure):  
    _fields_ = [  
        ("CV11", ctypes.c_double),   
        ("CV12", ctypes.c_double),   
        ("CV21", ctypes.c_double),  
        ("CV22", ctypes.c_double),   
        ("Reduce1", ctypes.c_uint8),   
        ("Reduce2", ctypes.c_uint8),   
        ("Power", ctypes.c_double)
    ]  
  
class ExtY_RPC_ABSmin_T(ctypes.Structure):  
    _fields_ = [  
        ("Number", ctypes.c_double)  
    ]
```

然后，读取共享链接库，并绑定数据：

```python
lib = ctypes.CDLL("RPC_ABSmin.dylib")  

RPC_ABSmin_U = ExtU_RPC_ABSmin_T.in_dll(lib, "RPC_ABSmin_U")  
RPC_ABSmin_Y = ExtY_RPC_ABSmin_T.in_dll(lib, "RPC_ABSmin_Y")
```

最后，执行仿真，对应`.c`文件：

```python
lib.RPC_ABSmin_initialize()  

RPC_ABSmin_U.CV11 = 1.0  
RPC_ABSmin_U.CV12 = 1.0  
RPC_ABSmin_U.CV21 = 1.0  
RPC_ABSmin_U.CV22 = 1.0  
RPC_ABSmin_U.Reduce1 = 0  
RPC_ABSmin_U.Reduce2 = 0  
RPC_ABSmin_U.Power = 2000.0  

for i in range(100):  
    lib.RPC_ABSmin_step()  
print("模型输出 Number =", RPC_ABSmin_Y.Number)  

lib.RPC_ABSmin_terminate()
```

## 移植到其他平台

在完成上述工作以后，需要将项目移植到其他平台，常见的服务器有 Windows 服务器和 Linux 服务器，而架构有 X86 架构和 ARM 架构，需要按照服务器的系统和架构编译对应版本的共享链接库。

由于 CMake 可以跨平台交叉编译，所以只需要进行一定的设置，就可以一次生成全平台的编译结果，方便我们进行移植。

### 安装编译器

本人安装过程中主要使用`homebrew`进行安装。

Windows X86_64 的编译器可以通过`homebrew`直接安装，方法如下：

```shell
brew install mingw-w64
```

Windows ARM64 的编译器无法直接通过`homebrew`安装，可以在 [Github](https://github.com/mstorsjo/llvm-mingw/) 上下载`llvm-mingw`的最新版本，然后解压到任意指定位置即可。

可以看到压缩包也包含了Windows X86_64 的编译器，所以也可以直接用这个，`homebrew`会同时配置好环境变量，更加方便一些。

![](https://img.papergate.top:5000/i/2025/11/691ab5ac2a816.webp)

Linux 的编译器可以通过`homebrew`直接安装，方法如下：

```shell
brew install FiloSottile/musl-cross/musl-cross
```

### 编写 `CMakePresets.json`

这个文件中定义了多种编译方式，以及各种编译方式的编译器和相关设置所在的位置：

```json
{  
  "version": 3,  
  "cmakeMinimumRequired": {  
    "major": 3,  
    "minor": 20,  
    "patch": 0  
  },  
  "configurePresets": [  
    {  
      "name": "macos-x86_64",  
      "displayName": "macOS x86_64",  
      "description": "macOS x86_64 build",  
      "generator": "Ninja",  
      "binaryDir": "${sourceDir}/build/macos-x86_64",  
      "cacheVariables": {  
        "CMAKE_BUILD_TYPE": "Release",  
        "CMAKE_OSX_ARCHITECTURES": "x86_64"  
      },  
      "environment": {  
        "CC": "/usr/bin/clang",  
        "CXX": "/usr/bin/clang++"  
      }  
    },  
    {  
      "name": "macos-arm64",  
      "displayName": "macOS ARM64",  
      "description": "macOS ARM64 build",  
      "generator": "Ninja",  
      "binaryDir": "${sourceDir}/build/macos-arm64",  
      "cacheVariables": {  
        "CMAKE_BUILD_TYPE": "Release",  
        "CMAKE_OSX_ARCHITECTURES": "arm64"  
      },  
      "environment": {  
        "CC": "/usr/bin/clang",  
        "CXX": "/usr/bin/clang++"  
      }  
    },  
    {  
      "name": "linux-x86_64",  
      "displayName": "Linux x86_64",  
      "description": "Linux x86_64 cross-compile",  
      "generator": "Ninja",  
      "binaryDir": "${sourceDir}/build/linux-x86_64",  
      "toolchainFile": "${sourceDir}/cmake/toolchains/linux-x86_64.cmake",  
      "cacheVariables": {  
        "CMAKE_BUILD_TYPE": "Release"  
      }  
    },  
    {  
      "name": "linux-arm64",  
      "displayName": "Linux ARM64",  
      "description": "Linux ARM64 cross-compile",  
      "generator": "Ninja",  
      "binaryDir": "${sourceDir}/build/linux-arm64",  
      "toolchainFile": "${sourceDir}/cmake/toolchains/linux-arm64.cmake",  
      "cacheVariables": {  
        "CMAKE_BUILD_TYPE": "Release"  
      }  
    },  
    {  
      "name": "windows-x86_64",  
      "displayName": "Windows x86_64",  
      "description": "Windows x86_64 cross-compile",  
      "generator": "Ninja",  
      "binaryDir": "${sourceDir}/build/windows-x86_64",  
      "toolchainFile": "${sourceDir}/cmake/toolchains/windows-x86_64.cmake",  
      "cacheVariables": {  
        "CMAKE_BUILD_TYPE": "Release"  
      }  
    },  
    {  
      "name": "windows-arm64",  
      "displayName": "Windows ARM64",  
      "description": "Windows ARM64 cross-compile",  
      "generator": "Ninja",  
      "binaryDir": "${sourceDir}/build/windows-arm64",  
      "toolchainFile": "${sourceDir}/cmake/toolchains/windows-arm64.cmake",  
      "cacheVariables": {  
        "CMAKE_BUILD_TYPE": "Release"  
      }  
    }  
  ]  
}
```

以上是本人使用的编译方式，从上到下定义了 MacOS、Linux 和 Windows 的多种编译：

- **MacOS的编译**：使用的是本地的编译器，位置在`/usr/bin/clang`和`/usr/bin/clang++`，可能会因为安装方式的不同而发生变化，本人的操作系统在如下位置。
- **Linux和Windows的编译**：这里使用了工具链（ToolChain）进行编译，可以看到工具链放在了`${sourceDir}/cmake/toolchains/`目录下，也就是在项目的根目录下有如下内容：

![](https://img.papergate.top:5000/i/2025/11/691aae80d278b.webp)

#### `linux-x86_64.cmake`

```cmake
set(CMAKE_SYSTEM_NAME Linux)  
set(CMAKE_SYSTEM_PROCESSOR x86_64)  
set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-linux-musl-gcc)  
set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-linux-musl-g++)  
set(CMAKE_SYSROOT /opt/homebrew)  
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)  
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)  
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
```

#### `linux-arm64.cmake`

```cmake
set(CMAKE_SYSTEM_NAME Linux)  
set(CMAKE_SYSTEM_PROCESSOR aarch64)  
set(CMAKE_C_COMPILER /opt/homebrew/bin/aarch64-linux-musl-gcc)  
set(CMAKE_CXX_COMPILER /opt/homebrew/bin/aarch64-linux-musl-g++)  
set(CMAKE_SYSROOT /opt/homebrew)  
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)  
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)  
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
```

#### `windows-x86_64.cmake`

```cmake
set(CMAKE_SYSTEM_NAME Windows)  
set(CMAKE_SYSTEM_PROCESSOR x86_64)  
set(CMAKE_C_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-gcc)  
set(CMAKE_CXX_COMPILER /opt/homebrew/bin/x86_64-w64-mingw32-g++)  
set(CMAKE_SYSROOT /opt/homebrew)  
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)  
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)  
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
```

#### `windows-arm64.cmake`

```cmake
set(CMAKE_SYSTEM_NAME Windows)  
set(CMAKE_SYSTEM_PROCESSOR ARM64)  
set(CMAKE_C_COMPILER   /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang)  
set(CMAKE_CXX_COMPILER /xxx/llvm-mingw/bin/aarch64-w64-mingw32-clang++)  
set(CMAKE_SYSROOT /xxx/llvm-mingw/aarch64-w64-mingw32)  
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)  
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)  
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
```

这里`llvm-mingw`的位置按照安装位置设定。

### 执行编译

执行编译的步骤如下：

```shell
# MacOS-x86_64
cmake --preset macos-x86_64
cmake --build build/macos-x86_64
# MacOS-arm64
cmake --preset macos-arm64
cmake --build build/macos-arm64
# Windows-x86_64
cmake --preset windows-x86_64
cmake --build build/windows-x86_64
# Windows-arm64
cmake --preset windows-arm64
cmake --build build/windows-arm64
# Linux-x86_64
cmake --preset linux-x86_64
cmake --build build/linux-x86_64
# Linux-arm64
cmake --preset linux-arm64
cmake --build build/linux-arm64
```



---

> 作者: Aphros  
> URL: https://blog.papergate.top/posts/%E4%BD%BF%E7%94%A8-python-%E8%B0%83%E7%94%A8-simulink-%E8%BF%9B%E8%A1%8C%E4%BB%BF%E7%9C%9F/  

