2. MATLAB数据接口核心机制

MATLAB 与外部环境交互,表面上是“调用一个函数、传一组数据、拿回一个结果”,底层却涉及三条彼此独立又相互衔接的通道:进程内二进制接口(MEX)、跨进程引擎接口(Engine API),以及数据序列化与内存所有权转移。理解这三者的分工,才能判断一个集成方案该用 MEX、该用 Engine,还是该走文件/网络序列化。

一、交互通道的底层分工

MEX 的本质是一个被 MATLAB 解释器按约定加载的动态链接库。MATLAB 在调用 MEX 函数时,把实参以 mxArray* 的形式传入,MEX 内部通过 mxGetPr、mxGetData、mxGetDimensions 等 API 读取数据指针与维度,计算完成后再用 mxCreateNumericArray 等函数构造返回值。MEX 运行在 MATLAB 进程内,因此没有跨进程拷贝开销,但一旦 MEX 内部崩溃,整个 MATLAB 会话随之崩溃。

Engine API 则相反:它把 MATLAB 作为一个独立进程启动,外部程序(C/C++、Python、Java)通过 engOpen、engEvalString、engPutVariable、engGetVariable 与这个进程通信。数据在外部程序与 MATLAB 工作区之间来回搬运,每次 engPutVariable 都会发生一次序列化与内存拷贝。它的优势是隔离性好、MATLAB 崩溃不会拖垮宿主程序,代价是延迟明显高于 MEX。

判断准则:需要高频、低延迟、紧耦合的数值计算,选 MEX;需要松耦合、跨语言、可独立重启的 MATLAB 能力复用,选 Engine API;只需要一次性交换大批量结构化数据,优先考虑 MAT 文件或 JSON/Parquet 等序列化格式。

二、mxArray 与内存所有权

无论走哪条通道,数据在 MATLAB 侧的统一表示都是 mxArray。它并不是一个简单的数组,而是一个包含类型、维度、数据指针、是否为复数、是否为稀疏、引用计数等字段的描述符结构。理解 mxArray 的关键在于“谁拥有这块内存”。

在 MEX 中,输入数组的内存由 MATLAB 拥有,MEX 只能读、不能释放;输出数组由 MEX 用 mxCreate* 创建,创建后所有权移交给 MATLAB,MEX 不得再 mxDestroyArray。这是 MEX 最常见的两类内存错误来源:误释放输入数组,或忘记创建输出数组导致返回空指针。

在 Engine API 中,engGetVariable 返回的 mxArray* 由调用方拥有,必须显式 mxDestroyArray;而 engPutVariable 之后,MATLAB 会复制一份数据,调用方仍可安全释放自己的数组。这一“谁创建、谁释放;谁接收、谁负责”的规则,是跨语言集成中内存泄漏与重复释放的根源。

三、数据序列化:从 mxArray 到字节流

跨进程或跨语言时,mxArray 必须先变成字节流。MATLAB 提供了两条成熟路径:MAT 文件(matOpen/matPutVariable,或 save -v7.3 的 HDF5 格式)与内存序列化(getByteStreamFromArray / getArrayFromByteStream)。前者适合持久化与大数据交换,后者适合在同一台机器上快速传递。

序列化时必须显式处理三件事:字节序(大端/小端)、数据类型映射(MATLAB 的 double 对应 C 的 double,但 logical 是 1 字节而非 4 字节)、维度顺序(MATLAB 列优先,C 行优先,跨语言传递二维数组时必须转置或明确约定)。忽略这三点,往往得到“数值全对但形状错乱”或“精度悄悄丢失”的结果。

MATLAB 与外部环境交互的三条通道与内存所有权 外部程序 C / C++ / Python / Java 宿主进程 拥有自己的堆内存 MEX 通道(进程内) mexFunction(mxArray*) mxGetPr / mxCreateNumericArray 零拷贝 · 崩溃即同归于尽 Engine 通道(跨进程) engOpen / engEvalString engPutVariable / engGetVariable 隔离性好 · 每次调用有拷贝 同进程 跨进程 IPC / 管道 MATLAB 解释器进程 工作区 Workspace · mxArray 描述符 · 引用计数 输入数组归 MATLAB 所有,MEX 只读;输出数组由 MEX 创建后移交 MATLAB 数据序列化层:mxArray ⇄ 字节流 MAT 文件 matOpen / matPutVariable save -v7.3 → HDF5 内存字节流 getByteStreamFromArray getArrayFromByteStream 通用格式 JSON / Parquet / HDF5 需显式处理字节序与维度 序列化三要素:字节序(大端/小端)· 类型映射(logical 为 1 字节)· 维度顺序(MATLAB 列优先 vs C 行优先)

四、内存管理的三条铁律

把上述机制落到代码上,可以归纳为三条可检查的规则。

  • 输入只读:MEX 中 prhs[i] 指向的内存归 MATLAB,任何 mxDestroyArray(prhs[i]) 都是未定义行为。
  • 输出移交:plhs[i] = mxCreateDoubleMatrix(...) 之后,MEX 不再持有该数组,不得再释放。
  • 获取即拥有:Engine 中 engGetVariable 返回的数组必须由调用方 mxDestroyArray;engPutVariable 之后调用方仍可释放自己的副本。

一个常见的教学示例是:外部程序用 Engine 把 1000×1000 的 double 矩阵送入 MATLAB,执行 B = A * A',再取回结果。若在循环中反复 engGetVariable 而不释放,内存会以每次约 8 MB 的速度增长;加上 mxDestroyArray 后,进程内存曲线趋于平稳。这个对比本身就是判断内存所有权是否处理正确的直接依据。

五、选型与验证

实际工程中,选型可以按“调用频率 × 数据量 × 隔离要求”三个维度判断:高频小数据用 MEX;低频大数据用 MAT 文件;需要独立重启、跨语言复用 MATLAB 工具箱能力时用 Engine API。验证时不要只看结果数值,还要观察三件事:进程内存是否随调用次数线性增长、返回数组的维度与类型是否与预期一致、以及 MATLAB 侧 whos 显示的变量是否在预期范围内被创建和清理。

调试 MEX 时,先用 mex -g 编译并保留符号,再在外部调试器中附加到 MATLAB 进程;调试 Engine 时,优先检查 engOpen 的返回值与 MATLAB 启动路径,很多“调用无响应”其实是引擎进程未能正常启动。

掌握这三条通道与内存所有权规则后,后续无论是封装 C 算法为 MEX、还是用 Python 驱动 MATLAB 做批量计算,都能在“数据怎么进、怎么出、谁负责释放”这一层保持清晰,避免集成中最难排查的内存与形状类错误。