简介:这份文档面向需要在 Visual Studio 中调用 MATLAB 生成 DLL 的 C++ 开发者,尤其适合做算法混合编程、科学计算或工程仿真的中高级读者。内容围绕 MATLAB 封装 DLL、VS 环境路径配置、C++ 调用 MATLAB 函数以及 mwArray 应用案例展开,并给出可运行的测试程序代码,帮助读者打通从 MATLAB 编译到 C++ 调用的完整链路。资源包共 1 个 docx 文件,约 1.08MB,以图文步骤与代码片段为主,便于对照操作。文档中涉及 VC++ 目录、链接器附加库目录与附加依赖项、mclmcr 系列库链接、mbuild -setup 配置编译语言等关键环节,还包含 mwArray 传参与函数调用格式的示例,可作为排错与验证的参考。目前已有 2553 人学习下载,适合需要快速上手 MATLAB 与 C++ 混合编程的读者查阅。
1. 从一份整理教程说起:VS 调用 MATLAB 生成 dll 到底难在哪
如果你手头有一份标着“整理教程”的 docx,里面写着 VS2017 调用 MATLAB 2018b 生成的 dll,大概率你正卡在某个环节:MATLAB 那边 library Compiler 打包完了,VS 这边一编译就报 LNK2019 找不到符号,或者程序跑起来直接弹“Could not initialize the application”。这份资源的价值不在于它教了什么新东西,而在于它把 MATLAB 封装 dll、VS 环境路径配置、mwArray 传参、初始化与终止顺序这几件事串成了一条能走通的链路。适合两类人:一类是算法在 MATLAB 里跑通了、想搬到 C++ 工程里做集成或交付的开发者;另一类是学生或刚转工程化的同学,需要把 .m 函数变成 dll 给上位机调用。它解决的核心问题是“MATLAB 函数怎么变成 C++ 能链接、能传参、能释放的二进制模块”,而不是教你 MATLAB 语法或 C++ 语法。下面按我实际拆包复现的顺序,把每一步的参数、路径和翻车点讲清楚。
2. MATLAB 侧封装 dll:library Compiler 的参数与 mbuild 环境
2.1 编译环境准备与 mbuild -setup 的真实作用
很多人拿到 MATLAB 后直接点 library Compiler,结果打包到一半报“No supported compiler was found”。这不是 MATLAB 的问题,是它没找到匹配的 C++ 编译器。MATLAB 2018b 支持的编译器版本有明确范围,VS2017 的 MSVC 工具集需要单独勾选 C++ 桌面开发工作负载,并且安装路径不能有中文和空格。我一般先在 MATLAB 命令窗口跑一次:
mbuild -setup这个命令会列出当前系统里 MATLAB 能识别的编译器。如果列表为空,说明 MATLAB 没探测到 VS 的编译器,常见原因是 VS 安装时没装“用于 CMake 的 Visual C++ 工具”或 Windows SDK 版本不匹配。选好编译器后,MATLAB 会把配置写进mbuildopts文件,后续 library Compiler 打包时直接复用这个配置。注意:mbuild -setup只影响 mbuild 和 mex,library Compiler 走的是同一套编译器探测逻辑,所以这一步过了,打包基本不会卡在编译器上。
2.2 library Compiler 打包 dll 的完整参数路径
打开 MATLAB 的 Apps 标签,找到 Library Compiler,或者直接在命令窗口输入libraryCompiler。界面里选 “C++ Shared Library”,然后把要导出的 .m 文件加进去。这里有个容易忽略的点:如果你的 .m 函数有多个输出参数,library Compiler 会自动生成对应的 C++ 接口,但函数名和参数顺序必须和 .m 文件里完全一致。比如一个加法函数:
function y = test(x1, x2) y = x1 + x2; end打包时在 “Exported Functions” 里把test加进去,右侧的 “Settings” 里选 “C++ Code”,不要选 “C Code”。C++ 接口会生成test.h、test.lib、test.dll和test.ctf四个关键文件。其中.ctf是 MATLAB 运行时需要的组件技术文件,里面打包了 .m 代码和依赖的 MATLAB 函数库。很多人只拷了 dll 和 lib,忘了 ctf,结果运行时报 “Undefined function or variable”,这就是 ctf 没放对位置。打包完成后,输出目录里会有一个for_redistribution_files_only文件夹,里面就是你要拷到 VS 工程里的全部文件。
2.3 生成文件的组成与放置策略
打包输出一般包含这几类文件:头文件(test.h)、导入库(test.lib)、动态库(test.dll)、组件技术文件(test.ctf),以及 MATLAB 运行时需要的mclmcrrt系列 dll。VS 工程里需要配置的是头文件路径、lib 路径和附加依赖项。我的习惯是把test.h放到工程目录下的include文件夹,test.lib放到lib文件夹,test.dll和test.ctf放到最终 exe 的同级目录。这样调试时不用改系统 PATH,直接相对路径就能加载。注意:test.ctf必须和 exe 在同一目录,或者放在 MATLAB Runtime 的搜索路径里,否则初始化会失败。
3. VS 工程配置:包含目录、库目录与附加依赖项
3.1 VC++ 目录与链接器常规的路径写法
打开 VS2017,新建一个空 C++ 控制台工程。右键工程属性,先看 “VC++ 目录” 下的 “包含目录”,把 MATLAB 安装目录下的extern\include加进去,再把刚才打包生成的test.h所在目录也加进去。接着看 “库目录”,把 MATLAB 的extern\lib\win64\microsoft加进去,再把test.lib所在目录加进去。这里有个版本匹配的坑:MATLAB 2018b 的extern\lib下分win32和win64,如果你的 VS 工程是 x64,就必须选win64,选错了会在链接时报 “模块计算机类型 x64 与目标计算机类型 x86 冲突”。我一般直接在属性页顶部把 “平台” 切到 x64,再配路径,避免来回改。
3.2 附加依赖项里那串 lib 的取舍
链接器 -> 输入 -> 附加依赖项里,需要填的 lib 分两组。第一组是 MATLAB 运行时库:
mclmcr.lib mclmcrrt.lib mclbase.lib mclcommain.lib libmex.lib libmx.lib libmat.lib libeng.lib第二组是你自己生成的test.lib。这里有个血泪经验:mclmcrrt.lib是运行时库的导入库,mclmcr.lib是初始化相关的库,两个都要加。只加mclmcrrt.lib会在mclInitializeApplication处报未解析的外部符号。另外,libeng.lib是 MATLAB 引擎接口,如果你只是调用生成的 dll,不直接开引擎,可以不加,但加上不会出错。附加依赖项里每行一个 lib,不要用逗号分隔,VS 会按行解析。
3.3 调试与发布时的 PATH 与 ctf 位置
编译通过后,直接 F5 运行,大概率会弹 “找不到 mclmcrrt9_5.dll” 之类的错误。这是因为运行时 dll 不在 exe 的搜索路径里。解决办法有两种:一种是把 MATLAB 的bin\win64加到系统 PATH,另一种是把需要的运行时 dll 拷到 exe 同级目录。我一般用后者,因为交付时不用改客户环境。需要拷的 dll 包括mclmcrrt9_5.dll、mclbase.dll、mclcommain.dll等,具体版本号看 MATLAB 安装目录下的bin\win64。同时确认test.ctf也在 exe 同级目录。如果运行时提示 “Could not initialize the application”,先检查mclInitializeApplication的返回值,再看 ctf 是否缺失。
4. C++ 调用侧:mwArray 传参、初始化顺序与终止释放
4.1 mwArray 的构造与索引赋值
MATLAB 生成的 C++ 接口用mwArray作为通用数组类型。构造一个 1x1 的 double 数组:
mwArray a(1, 1, mxDOUBLE_CLASS); mwArray b(1, 1, mxDOUBLE_CLASS); a(1, 1) = 1.8; b(1, 1) = 2.9;mwArray的构造函数第一个参数是行数,第二个是列数,第三个是类型常量。mxDOUBLE_CLASS对应 MATLAB 的 double。索引从 1 开始,不是 0,这点和 C++ 原生数组相反,写惯了 C++ 的人第一次很容易写成a(0,0),结果运行时越界或数据错乱。赋值时a(1,1) = 1.8会做类型检查,如果构造时用的是mxINT32_CLASS,赋 double 会截断。我一般先确认 MATLAB 函数里变量的类型,再决定mwArray的构造类型。
4.2 调用生成函数与输出参数顺序
假设 MATLAB 函数是y = test(x1, x2),生成的 C++ 接口签名是:
bool test(int nargout, mwArray& y, const mwArray& x1, const mwArray& x2);第一个参数nargout是输出参数个数,对于单输出函数填 1。第二个参数是输出,后面是输入。调用时:
mwArray z(1, 1, mxDOUBLE_CLASS); test(1, z, a, b);这里z是输出,a和b是输入。注意:z在调用前不需要赋初值,函数内部会重新构造它。如果 MATLAB 函数有多个输出,比如[y1, y2] = test(x1, x2),C++ 接口会变成test(2, y1, y2, x1, x2),nargout填 2。输出参数的顺序和 MATLAB 函数声明里的顺序一致,不能颠倒。
4.3 初始化与终止的配对逻辑
完整的调用流程必须包含初始化和终止:
if (!mclInitializeApplication(NULL, 0)) { cout << "Could not initialize the application.\n"; return -1; } bool isOk = testInitialize(); if (!isOk) { cout << "testInitialize failed\n"; return -1; } // ... 调用 test ... testTerminate(); mclTerminateApplication();mclInitializeApplication是全局初始化,整个进程只调一次。testInitialize是组件初始化,每个生成的 dll 有自己的初始化函数。testTerminate和mclTerminateApplication必须配对调用,否则进程退出时可能卡死或报内存泄漏。我见过有人在循环里反复调testInitialize和testTerminate,结果第二次初始化就失败,原因是 MATLAB 运行时不允许重复初始化同一个组件。正确做法是在程序启动时初始化一次,退出时终止一次。
4.4 完整测试程序与运行结果解读
把上面的片段拼起来,就是一个可运行的测试程序:
#include "test.h" #include <iostream> using namespace std; int main() { if (!mclInitializeApplication(NULL, 0)) { cout << "Could not initialize the application.\n"; return -1; } bool isOk = testInitialize(); if (!isOk) { cout << "testInitialize failed\n"; return -1; } mwArray a(1, 1, mxDOUBLE_CLASS); mwArray b(1, 1, mxDOUBLE_CLASS); a(1, 1) = 1.8; b(1, 1) = 2.9; mwArray z(1, 1, mxDOUBLE_CLASS); test(1, z, a, b); cout << a << "+" << b << "=" << z << endl; testTerminate(); mclTerminateApplication(); cout << "C++完成 matlab 的 dll 调用" << endl; cin.get(); return 0; }运行后输出1.8 + 2.9 = 4.7,说明调用链路通了。如果输出是0或者乱码,先检查mwArray的类型是否匹配,再看 MATLAB 函数里有没有对输入做类型转换。mclWaitForFiguresToDie只在 MATLAB 函数会弹图窗时需要,纯计算函数可以不加,加了反而会让程序卡住等图窗关闭。
5. 避坑与排查:链接、初始化、传参的常见翻车记录
5.1 链接时报 LNK2019 找不到符号
现象:编译通过,链接时提示error LNK2019: 无法解析的外部符号 _testInitialize或类似。原因:附加依赖项里漏了test.lib,或者 lib 的路径没加到库目录。解决:检查链接器 -> 输入 -> 附加依赖项里是否有test.lib,再检查库目录是否包含test.lib所在文件夹。如果 lib 是 x64 而工程是 Win32,也会报这个错,把工程平台切到 x64 即可。
5.2 运行时弹 Could not initialize the application
现象:程序启动后直接输出 “Could not initialize the application” 并退出。原因:mclInitializeApplication返回 false,通常是 MATLAB 运行时 dll 不在搜索路径,或者test.ctf缺失。解决:把 MATLABbin\win64下的mclmcrrt9_5.dll等运行时 dll 拷到 exe 同级目录,确认test.ctf也在同级目录。如果还不行,用 Dependency Walker 看 exe 依赖哪个 dll 没找到。
5.3 mwArray 索引越界或数据错乱
现象:调用函数后输出结果不对,或者程序崩溃在mwArray的析构处。原因:mwArray索引从 1 开始,写成了 0;或者构造时行列数和 MATLAB 函数期望的维度不一致。解决:统一用(1,1)索引,构造时确认行列数。如果 MATLAB 函数接受矩阵,C++ 侧也要构造对应维度的mwArray,不能只传标量。
5.4 忘记调用 testTerminate 导致进程卡死
现象:程序运行完输出结果后不退出,或者退出时卡住。原因:只调了mclTerminateApplication没调testTerminate,或者反过来。解决:确保testInitialize和testTerminate配对,mclInitializeApplication和mclTerminateApplication配对。如果程序里有多个生成的 dll,每个 dll 的 Initialize 和 Terminate 都要各自配对。
5.5 版本不匹配导致的运行时崩溃
现象:在开发机上跑得好好的,拷到另一台机器上就崩。原因:目标机器上的 MATLAB Runtime 版本和打包时用的 MATLAB 版本不一致。解决:MATLAB 2018b 生成的 dll 需要对应版本的 MATLAB Runtime,不能混用。要么在目标机器上装同版本 Runtime,要么把运行时 dll 一起打包。注意:mclmcrrt9_5.dll里的9_5对应 MATLAB R2018b,版本号不对会直接加载失败。
6. 进阶技巧:多函数导出、结构体传参与运行时精简
6.1 一次打包多个函数与命名空间隔离
library Compiler 支持一次添加多个 .m 文件,生成的test.h里会包含所有导出函数的声明。如果函数名有冲突,可以用命名空间隔离。在 library Compiler 的 “Settings” 里可以设置 “Library Name”,生成的 C++ 接口会以这个库名作为命名空间。比如库名设为MyLib,调用时写MyLibInitialize()和MyLibTerminate()。我一般把相关的几个函数打成一个库,减少 dll 数量,也方便统一初始化。
6.2 结构体与元胞数组的 mwArray 构造
MATLAB 函数如果接受结构体,C++ 侧需要用mwArray的GetField和SetField方法。比如 MATLAB 里s.a = 1; s.b = 2;,C++ 侧:
mwArray s(1, 1, mxSTRUCT_CLASS); s.SetField("a", mwArray(1.0)); s.SetField("b", mwArray(2.0));元胞数组用mxCELL_CLASS,通过s(1,1) = mwArray(...)赋值。注意:结构体字段名必须和 MATLAB 里完全一致,大小写敏感。如果 MATLAB 函数内部对结构体字段做了类型检查,C++ 侧构造的mwArray类型也要匹配。
6.3 运行时依赖精简与部署检查清单
交付时不需要把整个 MATLAB 安装目录拷过去,只需要bin\win64下的运行时 dll 和生成的test.dll、test.ctf。我一般用 Dependency Walker 或dumpbin /dependents看 exe 依赖哪些 dll,然后从 MATLAB 安装目录里挑出来。一个常见的精简清单:
| 文件 | 来源 | 作用 |
|---|---|---|
| test.dll | 打包输出 | 你的 MATLAB 函数 |
| test.ctf | 打包输出 | 组件技术文件 |
| mclmcrrt9_5.dll | MATLAB bin\win64 | 运行时核心 |
| mclbase.dll | MATLAB bin\win64 | 基础库 |
| mclcommain.dll | MATLAB bin\win64 | 通信库 |
| mclmcr.dll | MATLAB bin\win64 | 初始化库 |
把这几个文件放在 exe 同级目录,基本就能在没装 MATLAB 的机器上跑起来。如果目标机器是 Windows Server,还需要确认 VC++ 运行库是否安装,因为 MATLAB 运行时依赖 MSVC 的运行时组件。
6.4 一个我常用的验证习惯
每次打包完 dll,我不会直接往大工程里塞,而是先建一个空的控制台工程,只调一个最简单的加法函数,确认初始化、传参、终止、输出四步都通。这个习惯帮我省了很多“在大工程里排查半天发现是 ctf 没拷”的时间。从那以后我每次拿到新的 MATLAB 打包 dll,都强制走一遍这个最小验证流程,确认无误再集成。希望帮到你。
本文还有配套的精品资源,点击获取