下面我将从核心概念、主要方法、详细步骤、完整实例以及常见问题等多个方面,为你全面讲解 C 语言与 MATLAB 的接口编程。

核心概念:为什么需要接口?
- 性能瓶颈:MATLAB 是解释型语言,对于大规模循环、底层算法(如信号处理、图像处理的核心算法),其执行效率远低于编译型语言 C/C++。
- 硬件访问:MATLAB 难以直接访问硬件(如数据采集卡、嵌入式设备),C/C++ 可以通过驱动程序直接与硬件交互。
- 代码复用:你可能已经有一个用 C/C++ 编写的、经过充分测试和优化的成熟算法库,希望在 MATLAB 中直接调用,而无需重新用 MATLAB 实现。
- 并行计算:可以利用 C/C++ 结合 OpenMP 或 MPI 等多线程/多进程库进行并行计算,然后将结果返回给 MATLAB。
核心思想:编写一个 C/C++ 函数,并按照 MATLAB 的规则进行“包装”,使其可以被 MATLAB 识别和调用,这个包装过程就是接口编程。
主要方法
目前最主流、最推荐的方法是 MEX (MATLAB Executable)。
MEX (MATLAB Executable)
- 是什么:MEX 是一种特殊的动态链接库(在 Windows 上是
.dll,在 Linux/macOS 上是.mex或.so),MATLAB 可以像调用内置函数一样直接加载并执行 MEX 文件中的 C/C++ 函数。 - 优点:
- 无缝集成:与 MATLAB 语法结合最紧密,可以方便地传递和接收各种 MATLAB 数据类型(矩阵、字符串、结构体等)。
- 双向数据交换:可以轻松地将数据从 MATLAB 传递给 C,并将计算结果从 C 返回给 MATLAB。
- 官方支持:MATLAB 提供了完整的 API 和工具,是官方推荐的方式。
- 缺点:
- 学习曲线稍陡峭,需要理解 MATLAB 的数据结构和 API。
- 生成的 MEX 文件与 MATLAB 版本和操作系统强相关,不能跨平台通用。
MATLAB Engine API
- 是什么:允许你在 C/C++ 程序中启动一个 MATLAB 进程,并直接调用 MATLAB 的函数和脚本。
- 优点:
- 可以在 C/C++ 程序中无缝调用 MATLAB 的所有内置函数和工具箱。
- 适合构建一个 C/C++ 的主应用程序,并利用 MATLAB 作为其计算引擎。
- 缺点:
- 启动开销大:每次调用都需要启动一个 MATLAB 进程,不适合高频、实时的函数调用。
- 性能开销比 MEX 高。
共享库 / 动态链接库
- 是什么:将 C/C++ 代码编译成一个标准的
.dll(Windows) 或.so(Linux) /.dylib(macOS) 文件,然后在 MATLAB 中使用loadlibrary和calllib等函数进行调用。 - 优点:
- 通用性强:生成的库文件可以被任何支持该库的语言(如 Python, Java, C#)调用,不局限于 MATLAB。
- 接口相对简单:如果只传递简单的数据类型(如
double,int),设置起来比较直接。
- 缺点:
- 数据类型转换复杂:传递 MATLAB 的
mxArray(矩阵)等复杂数据类型非常麻烦,需要手动进行内存拷贝和格式转换,容易出错。 - 功能受限:无法直接利用 MATLAB 的高级特性。
- 数据类型转换复杂:传递 MATLAB 的
对于绝大多数“在 MATLAB 中调用 C/C++ 算法”的场景,MEX 是最佳选择,本文将重点介绍 MEX 编程。
MEX 编程详解
工作流程
- 编写 C/C++ 源代码:实现你的核心算法,需要包含
mex.h头文件,并编写一个特殊的入口函数。 - 编写 Gateway 函数:这是连接 MATLAB 和 C/C++ 代码的桥梁,它的签名必须是固定的:
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]);
nlhs: MATLAB 调用时代号的输出参数数量 (Left-hand side)。plhs: 指向输出参数数组的指针 (mxArray*是 MATLAB 所有数据类型的基类)。nrhs: MATLAB 调用时代号的输入参数数量 (Right-hand side)。prhs: 指向输入参数数组的指针。
- 编译 MEX 文件:在 MATLAB 命令行中使用
mex编译器命令将你的 C/C++ 源代码编译成 MEX 文件。 - 在 MATLAB 中调用:像调用普通函数一样调用生成的 MEX 文件。
关键 API和数据类型
mxArray:MATLAB 中所有数据类型(数值矩阵、字符串、 cell 数组、结构体等)在 C API 中的表示,你可以把它理解为一个不透明的指针,MATLAB 内部知道如何处理它。- 创建和销毁
mxArray:mxCreateDoubleMatrix(m, n, mxREAL/mxCOMPLEX): 创建double类型的矩阵。mxCreateString(str): 创建字符串。mxDestroyArray(p): 销毁一个mxArray并释放其内存。非常重要!防止内存泄漏。
- 访问和修改
mxArray数据:mxGetPr(p): 获取实数部分的指针 (double*)。mxGetPi(p): 获取虚数部分的指针 (double*)。mxGetNumberOfElements(p): 获取元素总数。mxGetM(p): 获取行数。mxGetN(p): 获取列数。
- 参数检查:
mxIsDouble(p),mxIsChar(p)等:检查数据类型。mxGetNumberOfElements(p): 检查维度。- 在
mexFunction开头进行严格的参数检查,是编写健壮 MEX 代码的关键。
完整实例:C 实现矩阵相加,MATLAB 调用
这个例子将展示最经典、最常见的用法:将两个 double 类型的矩阵从 MATLAB 传入 C 函数,在 C 中完成矩阵加法,然后将结果矩阵返回给 MATLAB。

步骤 1:编写 C 源代码 (matrix_add.c)
#include "mex.h"
// mexFunction 是 MEX 文件的入口点
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
// 1. 检查输入参数数量
// 我们期望有2个输入矩阵 (nrhs=2) 和1个输出矩阵 (nlhs=1)
if (nrhs != 2) {
mexErrMsgTxt("Two input matrices required.");
}
if (nlhs > 1) {
mexErrMsgTxt("Too many output arguments.");
}
// 2. 检查输入参数是否为 double 类型矩阵
if (!(mxIsDouble(prhs[0]) && mxIsDouble(prhs[1]))) {
mexErrMsgTxt("Input matrices must be of type double.");
}
// 3. 获取输入矩阵的维度
size_t m = mxGetM(prhs[0]); // 行数
size_t n = mxGetN(prhs[0]); // 列数
// 检查两个矩阵维度是否一致
if (m != mxGetM(prhs[1]) || n != mxGetN(prhs[1])) {
mexErrMsgTxt("Input matrices must have the same dimensions.");
}
// 4. 创建输出矩阵
// mxCreateDoubleMatrix 创建一个 m x n 的双精度矩阵
plhs[0] = mxCreateDoubleMatrix(m, n, mxREAL);
// 5. 获取输入和输出数据的指针
double *A = mxGetPr(prhs[0]); // 指向第一个输入矩阵的数据
double *B = mxGetPr(prhs[1]); // 指向第二个输入矩阵的数据
double *C = mxGetPr(plhs[0]); // 指向输出矩阵的数据
// 6. 执行核心计算:矩阵加法
for (size_t i = 0; i < m * n; i++) {
C[i] = A[i] + B[i];
}
}
步骤 2:在 MATLAB 中编译
-
确保 MATLAB 的 C/C++ 编译器已正确配置,在 MATLAB 命令行中运行:
mex -setup
然后按照提示选择你的编译器(如 Microsoft Visual C++ Compiler、MinGW 等)。
-
将
matrix_add.c文件放在你的 MATLAB 当前工作目录下。 -
在 MATLAB 命令行中运行
mex命令进行编译:mex matrix_add.c
编译成功后,会在同一目录下生成一个名为
matrix_add.mexw64(Windows) 或matrix_add.mexa64(Linux) 的文件。
步骤 3:在 MATLAB 中调用
你可以像调用普通 MATLAB 函数一样使用它了。
% 创建两个 3x3 的随机矩阵
A = rand(3);
B = rand(3);
% 调用我们刚刚编译的 MEX 函数
C_mex = matrix_add(A, B);
% 为了验证,我们同时用 MATLAB 内置函数计算
C_matlab = A + B;
% 比较结果,理论上差值应该非常小(浮点数精度误差)
difference = C_mex - C_matlab;
disp('Difference between MEX and MATLAB result:');
disp(difference);
% 检查差值的范数,应该接近于 0
disp('Norm of the difference:');
disp(norm(difference));
输出结果:
由于浮点数精度问题,差值不会严格为零,但其范数会非常小(1e-15 或 1e-16 级别),证明我们的 MEX 函数工作正常。
进阶实例:传递字符串和结构体
传递字符串
在 C 中,MATLAB 字符串是一个 mxArray,其数据类型为 mxCHAR_CLASS,你需要用 mxGetChars 来获取字符指针。
C 代码示例 (print_string.c):
#include "mex.h"
#include <stdio.h>
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
if (nrhs != 1) {
mexErrMsgTxt("One input string required.");
}
if (!mxIsChar(prhs[0])) {
mexErrMsgTxt("Input must be a string.");
}
// 获取字符串指针
char *str = mxArrayToString(prhs[0]); // mxArrayToString 会处理内存分配
if (str == NULL) {
mexErrMsgTxt("Could not convert mxArray to string.");
}
// 在 C 控制台打印
mexPrintf("Received string from MATLAB: %s\n", str);
// 释放 mxArrayToString 分配的内存
mxFree(str);
}
MATLAB 调用:
mex print_string.c;
print_string("Hello from MATLAB!");
输出:
Received string from MATLAB: Hello from MATLAB!
传递结构体
结构体在 C 中处理起来比较复杂,你需要创建一个 struct 类型的 mxArray,然后为其添加字段。
C 代码示例 (create_struct.c):
#include "mex.h"
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
// 创建一个 1x1 的结构体
plhs[0] = mxCreateStructMatrix(1, 1, 0, NULL); // 0个初始字段
// 添加一个名为 "name" 的字符串字段
const char *field_names[1] = {"name"};
mxAddField(plhs[0], field_names[0]);
mxArray *name_array = mxCreateString("John Doe");
mxSetFieldByNumber(plhs[0], 0, 0, name_array); // 设置第0个结构的第0个字段
// 添加一个名为 "age" 的数值字段
const char *field_names2[1] = {"age"};
mxAddField(plhs[0], field_names2[0]);
mxArray *age_array = mxCreateDoubleScalar(30);
mxSetFieldByNumber(plhs[0], 0, 1, age_array); // 设置第0个结构的第1个字段
}
MATLAB 调用:
mex create_struct.c; s = create_struct(); disp(s);
输出:
name: 'John Doe'
age: 30
常见问题与最佳实践
-
内存管理:
- 谁分配,谁释放:MEX 函数为
plhs创建了新的mxArray,MATLAB 会在不需要时自动释放它。但是,如果你在 MEX 内部使用了malloc/new分配的内存,你必须在 MEX 函数结束前用free/delete释放它。 - 避免内存泄漏:使用
mexAtExit注册一个清理函数,在 MEX 文件被卸载时执行。 mxFreevsfree:mxFree用于释放由 MATLAB API (如mxArrayToString) 分配的内存。free用于释放由malloc/calloc分配的内存,不要混用。
- 谁分配,谁释放:MEX 函数为
-
多线程:
- MEX 默认不是线程安全的,如果你在 MEX 函数中创建了新的线程,必须非常小心。
- 从 MATLAB R2025a 开始,引入了对 MEX 的多线程支持,可以通过设置
mex编译选项-largeArrayDims和使用特定的 API 来安全地使用多线程。 - 对于复杂的并行计算,通常推荐将计算密集型任务交给一个单独的、完全线程化的 C++ 库,然后通过 MEX 调用这个库的接口。
-
性能优化:
- 减少数据拷贝:MEX 的主要开销在于数据在 MATLAB 和 C 之间的传递,尽量传递数据的指针而不是拷贝整个数据块。
- 向量化 C 代码:在 C 循环中,尽量使用连续的内存访问模式,这对 CPU 缓存更友好。
- 使用 C++:对于复杂的逻辑,C++ 的面向对象特性可以使代码更清晰、更易于维护。
-
调试:
mexPrintf:这是你在 MEX 代码中打印调试信息的最主要工具,输出会显示在 MATLAB 命令窗口。- IDE 调试:可以将 Visual Studio (Windows) 或 GDB (Linux) 作为 MEX 的调试器,在
mex命令中使用-g选项生成包含调试信息的 MEX 文件,然后在 IDE 中附加到 MATLAB 进程进行断点调试。
希望这份详细的指南能帮助你掌握 C 语言与 MATLAB 的接口编程!从简单的矩阵运算开始,逐步尝试更复杂的数据类型和算法,你会发现这是一个能极大提升你项目能力的强大工具。
