资讯动态

MATLAB生成DLL在VS2017中的调用:原理、配置与mwArray实战

发布时间:2026/10/16 1:22:25 来源:尧图企业网站定制
简介面向需要在 Visual Studio 中调用 MATLAB 生成 DLL 的 C 开发者这份整理教程系统梳理了从 MATLAB 封装到 VS 调用的完整链路设置编译环境、选择 C 代码生成、通过 Library Compiler 完成打包随后在 VS 中配置 VC 目录、链接器附加库目录与附加依赖项并执行 mbuild -setup 指定编译语言。资源以 1 个 docx 文档承载压缩包约 1.08MB适合作为混编开发的快速查询手册。文档内附完整测试程序源码演示 mwArray 数组类的赋值与函数调用例如对 a、b 赋值后通过 test(1,z,a,b) 完成加法运算同时覆盖 mclInitializeApplication、testInitialize、testTerminate 等关键接口并列出 mclmcr.lib、libmx.lib、libmat.lib 等必需的链接库文件针对环境初始化失败等常见问题也提供了排查思路。已有 2553 人学习对于 MATLAB 与 C 交互开发的初学者或需要快速落地 DLL 调用的工程人员能显著缩短环境配置与代码调试时间。1. 算法写好了产品端怎么把 MATLAB 函数用起来做算法的人大概率都遇到过这个场景MATLAB 里跑通了一段计算仿真结果也很漂亮但产品端是 C 写的总不能为了一个函数把整套 MATLAB 运行时都塞进客户机器里。这几年我拆过不少 MATLAB 生成 DLL 再被 VS 调用的案例结论是这条路完全走得通但中间环节确实多MATLAB 端要封装、VS 端要配路径、链接器要挂 lib、代码里还得处理 mwArray 这种特殊类型任何一个环节脱节都会翻车。这篇笔记围绕 VS2017 调用 MATLAB 2018b 生成的 DLL 这条主线把封装步骤、环境配置、调用格式和几个容易踩的坑完整捋一遍适合正在做跨语言集成的工程师也适合第一次接触 mwArray 的初学者照着操作。2. MATLAB 端封装 DLL先搞清楚 mcc 和 MCR 的关系再动手2.1 封装原理为什么 DLL 不能脱离 MCR 单独运行MATLAB 生成 DLL 的底层工具不是编译器本身而是 mcc它负责把 .m 函数翻译成 C 共享库。这里有一个非常关键的概念生成的 DLL 并不包含 MATLAB 的数学内核它只是对 MATLAB RuntimeMCR的调用封装。也就是说目标机器上如果没有安装对应版本的 MCRDLL 就算拷贝过去也跑不起来会直接报“无法加载 DLL”或者入口点找不到。这个机制决定了封装前的环境准备是有优先级的。我一般会先执行一次 mbuild -setup把编译语言指向已经安装的 C 编译器。在 MATLAB 命令行窗口输入这个命令系统会列出可用编译器选择 Microsoft Visual C 2017 对应的选项即可。这一步如果跳过后面 Library Compiler 界面里可能找不到可用的 C 编译链路封出来的 DLL 在 VS 里链接时会报一堆莫名其妙的 LNK 错误。除了 mbuild -setup还要顺手确认一下 mex -setup C两者指向同一个编译器工具链。常见做法是直接看 MATLAB 的“预设”里 C/C 编译器是否已经识别到 VS2017识别到了再走封装流程会顺畅得多。2.2 Library Compiler 封装步骤选对“C Shared Library”而不是别的在 MATLAB 的 App 选项卡里找到 Library Compiler打开后左侧需要选择 Library Type。这里最容易选错如果选了 Windows Dynamic Library生成的是 C 接口要做 C 调用必须选择 C Shared Library。主界面里把写好的 .m 文件添加进“Exported Functions”区域比如我经常拿来做演示的 test.mfunction Y test(X1, X2) Y X1 X2; end然后点击右侧的 Settings 齿轮图标在 Language 里确保选的是 C这样生成的 .h 头文件里会自动带上 mwArray 相关的类型声明。最后点 Package 按钮MATLAB 会执行编译并生成一个文件夹里面包含的头文件 test.h、动态库 test.dll、导入库 test.lib以及一个可选的 setup 脚本。这里要留意一个细节生成的文件夹里通常还包含一个子文件夹里面放着 for_redistribution 和 for_testing 两份内容。VS 做开发调试时用的是 for_testing 里面的 DLL 和 lib如果要打包发布给没有 MATLAB 的机器需要运行 for_redistribution 里的安装包那个安装包会把 MCR 运行时一并装到目标机器上。开发阶段先用 for_testing 目录下的文件就够了。封装完成后可以在 MATLAB 命令行里输入!dir查看当前工作目录确认 test.h、test.dll、test.lib 三个文件是否已经生成。这三个文件是后续 VS 工程里最核心的三样东西缺一个都没法往下走。3. VS2017 环境配置包含目录、附加库目录和链接器依赖项3.1 配置目录的四个通道工程属性里到底要填哪些路径第一步是打开 VS2017创建一个空的 C 控制台应用程序然后把 MATLAB 生成的 test.h、test.dll、test.lib 拷贝到项目目录下我一般习惯在工程文件夹里建一个 external 目录专门放这些外部依赖这样整个项目复制到别的电脑上时不会因为绝对路径失效而报错。接着打开项目属性页先找到“VC 目录”这个配置项。这里需要填两个路径一个是“包含目录”要指向 MATLAB 安装目录下的 extern\include 文件夹因为 test.h 内部会 include MATLAB 提供的头文件另一个是“库目录”要指向 extern\lib\win64\microsoft 文件夹这里放着 mclmcrrt.lib 等 MATLAB 运行时库的导入库文件。这里有一个小知识点VS 里的“VC 目录”和“链接器-常规-附加库目录”功能上有重叠。我常用的分工是在 VC 目录里填 MATLAB 自带头文件和库文件的路径在链接器里填我自己的库文件路径比如项目里的 external 目录。当然有工程师喜欢把路径全部塞进链接器里也完全没问题关键在于保证所有需要的路径都被覆盖到。第二步是配置链接器。打开“链接器-常规-附加库目录”把我拷贝了 .lib 文件的路径填进去再打开“链接器-输入-附加依赖项”把需要链接的 lib 文件名称逐行加上。这里不是只加 test.lib 就完事的MATLAB 生成的 C 库还依赖一组 MATLAB 运行时的 lib我的经验是完整的依赖清单可以参照下表配置。lib 文件名称作用说明mclmcr.libMATLAB C 共享库运行时入口mclmcrrt.lib运行时核心库通常已经包含在 mclmcr 中mclbase.lib基础服务层初始化环境时用到mclcommain.lib命令行应用辅助库libmex.libMEX 接口兼容库libmx.lib矩阵数据类型的底层库libmat.libMATLAB 数组访问接口libeng.lib引擎调用支持非必须但建议加上test.lib自己封装的函数的导入库注意这里有个细节这些 lib 文件大多数是 64 位版本如果 VS 工程配置的是 Win32 平台链接时会报“无法打开文件 mclmcr.lib”因为 win64 目录下根本不存在 32 位库。所以一旦看到这类错误先检查平台是不是被切到 x64 了。3.2 Release / Debug 一致性和位数检测编译不通过时先查这两项配置完路径后编译最常见的报错有两类一类是 LNK 1104 找不到某个 .lib 文件这种通常是附加库目录没配对或者文件名拼写有误另一类是 LNK 2019 无法解析的外部符号这种问题往往出在 Release 和 Debug 模式下链接的运行时库不一致。MATLAB 生成的 DLL 默认是 Release 编译的VS 工程如果跑在 Debug 模式下配置的“运行时库”如果是 /MTd 或者 /MDd就有可能出现 mwArray 相关符号能声明但链接不过的情况。我的做法是整个项目统一使用 Release 模式调试。如果确实需要在 Debug 模式下跑可以去 MATLAB 安装目录下找是否有对应的 debug 版本的运行时库但一般情况下不值得折腾直接用 Release 就好。另外有个简单的验证方法打开 VS 的“生成-输出”窗口看链接命令里实际使用的 lib 路径是不是我们配置的那些目录。如果链接命令里出现的是 C:\Windows\System32 或者别的奇怪路径说明附加库目录的顺序或者全局搜索路径有干扰把项目自己的路径往前提即可。配置完成后再编译一次如果能够顺利生成 .exe就说明路径这块已经通了接下来进入代码层面。4. C 调用代码与 mwArray 用法从初始化到释放的完整链路4.1 调用格式的核心约定三个参数位置的含义在写代码之前先理解 MATLAB 生成的函数的调用格式。比如 test.m 的签名是 Y test(X1, X2)那么在生成的 test.h 里对应的函数声明大致是void test(int nlhs, mwArray Y, const mwArray X1, const mwArray X2);这里 nlhs 表示输出参数的个数之后依次是输出参数、输入参数。MATLAB 生成的头文件里每个导出的函数都遵循这个约定第一个参数是输出数量紧接着是输出参数列表再往后才是输入参数列表。这个格式很容易记错我最开始就把输出参数漏掉了结果调用后 z 里始终是初始值。理解了签名之后再来看完整的调用流程。代码的骨架分为五个阶段初始化 MATLAB 应用环境、初始化具体函数库、构造输入输出数据、调用函数、释放环境。下面这段代码是完整的测试程序可以直接复制到 VS 工程中使用。4.2 可运行的测试代码及逐段拆解#include test.h #include iostream using namespace std; int main() { bool isOk 0; if (!mclInitializeApplication(NULL, 0)) { cout Could not initialize the application.\n; return -1; } cout isOk isOk endl; // 此时尚未初始化函数库 isOk testInitialize(); cout isOk isOk endl; // 返回 true 表示函数库就绪 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(); std::cout C完成matlab的dll调用 std::endl; int aa; std::cin aa; return 0; }逻辑说明第一段 mclInitializeApplication(NULL, 0) 是启动 MATLAB 运行时环境NULL 表示没有任何启动选项0 表示选项个数为 0。这一步如果不做后面所有 mwArray 的构造和调用都会在运行时崩溃而且不会给出具体提示这是最容易踩的坑之一。第二段 testInitialize() 是对当前 DLL 的函数库做初始化。这里返回的 isOk 才是真正意义上的“库是否就绪”标志。在调用任何导出函数之前两个初始化都必须成功。代码里加了一段 cout 打印 isOk 的中间值是刻意展示初始化前后的状态变化方便新手确认执行到的位置。第三段 mwArray 的构造是重点。mwArray a(1, 1, mxDOUBLE_CLASS) 表示创建一个 1 行 1 列的 double 类型数组后面的括号索引 a(1, 1) 是 MATLAB 风格的下标注意不是 C 风格的 [0][0]而是从 1 开始。赋值 1.8 和 2.9 之后调用 test(1, z, a, b)第一个参数 1 表示函数返回一个输出z 是输出参数a 和 b 是传给 MATLAB 函数的输入。输出参数 z 在调用前也需要用 mwArray 构造出来虽然它的值会被函数覆盖但不构造直接传引用会触发访问冲突。这段代码里 z 的构造维度是 (1, 1, mxDOUBLE_CLASS)与预期返回的标量一致。如果 MATLAB 函数返回的是一个向量或矩阵这里就要按实际尺寸构造 mwArray。最后一段 testTerminate() 和 mclTerminateApplication() 是对应的释放函数前者释放 DLL 函数库的资源后者关闭 MATLAB 运行时顺序不能颠倒。我见过有同学只写 mclTerminateApplication 不写 testTerminate程序虽然能退出但会有内存泄漏警告。4.3 mwArray 赋值和读取的进阶细节mwArray 的操作符重载做得比较完整除了第一维索引赋值还可以用 GetData 方法读取数据double result 0.0; z.GetData(1, 1, result); cout result result endl;GetData 的第一个参数是输出类型指针或者标量引用后面的参数是索引位置。如果 z 是一个矩阵需要先调用 z.NumberOfElements() 获取元素个数再循环读出。这个接口在没有 MATLAB 环境的情况下也可以使用因为它依赖的仅仅是运行时库。对于多维数组的场景比如 MATLAB 函数返回一个 2 行 3 列的矩阵构造时可以直接写 mwArray m(2, 3, mxDOUBLE_CLASS)读取时按行列索引 m.GetData(1, 1)、m.GetData(1, 2) 依次取。这里要注意 mwArray 的存储顺序和 MATLAB 相同是列优先和 C/C 的习惯相反写循环时容易搞混建议先打印出来看一遍再写逻辑。5. 避坑从 DLL 加载失败到数据错乱的六个翻车现场5.1 现象“无法加载 DLL”或者“找不到指定模块”原因MCR 没有安装或者装了但是版本和编译时不一致。这里说的版本不一致不只是 2018b 和 2019b 这种大版本差异即使是同一大版本下的小更新运行时也可能不兼容。解决开发机上确认 MATLAB 自带运行时目录已经存在比如 C:\Program Files\MATLAB\MATLAB Runtime\v95 这种路径是否可访问。如果目标机器要部署就用 for_redistribution 目录里的 exe 安装一遍运行时不要试图手动拷贝 mclmcrrt.dll 来省事这个 DLL 本身还依赖其他组件拷贝不全照样起不来。5.2 现象编译报了 LNK 2019说不认识 mwArray 相关符号原因最常见的情况是包含目录指到了 extern\include\win64 而不是 extern\include导致头文件里条件编译没有打开 64 位分支类型定义和链接库对不上。解决在 VC 包含目录里同时加上 extern\include 和 extern\include\win64 两个路径顺序不要紧但两个都不能少。另外检查预处理定义里是否有 _WINDOWS 相关的宏MATLAB 的头文件在某些宏缺失时会跳过一部分声明加上 WIN32 和 _WINDOWS 后重新编译问题基本能消除。5.3 现象程序编译和链接都过了一运行就崩崩溃位置指向 mclInitializeApplication原因初始化调用放在了静态变量构造之后或者 mwArray 类型的全局对象先被构造了。MATLAB 的运行时环境要求在第一次创建任何 mwArray 对象之前完成初始化。解决不要在全局作用域声明 mwArray 类型的变量把所有 mwArray 的使用都在 mclInitializeApplication 之后进行。如果工程里非要保留全局对象改成指针形式然后在 main 里手动 new也算是一种绕法。5.4 现象调用 test() 之后 z 的值始终是 0不报错也不崩溃原因nlhs 参数写错。比如 MATLAB 函数返回两个值但在 C 里只给了 1 个输出参数位置生成的函数内部可能只会取第一个输出槽位第二个参数被当成输入使用了。更隐蔽的情况是 nlhs 传了 0函数被当成“无输出调用”z 自然不会被写入。解决先在 MATLAB 命令行里用 nargout 查看函数实际输出个数再检查 C 调用语句里 nlhs 是否与该值一致。我自己反复出错后养成了一个习惯每写一个调用就先数一下参数个数从左往右依次是 nlhs、输出、输入从来不看函数文档直接对照生成的头文件。5.5 现象编译到一半报“检测到 _ITERATOR_DEBUG_LEVEL 不匹配”原因这是 Debug 和 Release 的运行时库混用导致的经典报错。MATLAB 的运行时库是 Release 编译的VS 工程开了 Debug 模式并且预处理器里 _DEBUG 被定义STL 容器和迭代器的调试级别不一致。解决直接把整个工程切换到 Release 模式或者在 Debug 模式下关闭 _DEBUG 宏不推荐。最省心的是确认活动解决方案配置是 Release然后重新编译。5.6 现象x64 平台编译成功了换到 x86 就各种打不开原因MATLAB 从 2017 版本开始逐步放弃 32 位支持生成的 DLL 只有 64 位版本。VS 工程如果默认创建的是 Win32 平台链接时会在 win64 路径下找不到 lib 文件。解决进入“配置管理器”把活动解决方案平台切换为 x64。如果列表里没有 x64先新建一个平台再重新配置包含目录和附加库目录因为不同平台下路径配置是独立的切换后需要重新检查一遍。6. 进阶从控制台测试到工程集成的三个关键习惯当控制台测试程序能顺利算出 1.8 2.9 4.7 之后真正的工程场景才算开始。实际项目里很少直接在一个 main 函数里完成所有事情更多是把 MATLAB 的 DLL 封装进一个 C 类在业务代码里创建对象、调用方法、释放资源。这个过程中有几个习惯非常值得固定下来。第一个习惯是用 mclRunMain 代替裸写 main 入口。mclRunMain 是 MATLAB 提供的一个辅助函数它会自动处理 mclInitializeApplication 的前置条件并且保证在 main 真正执行前初始化工作已经完成。写法大致是int runMain(int argc, char** argv) { // 业务逻辑里可以直接构造 mwArray return 0; } int main(int argc, char** argv) { return mclRunMain((mclMainFcnType)runMain, argc, argv); }把业务逻辑放进 runMain 之后不再需要在代码里手动调 mclInitializeApplication 和 mclTerminateApplication这两个由 mclRunMain 负责兜底。如果未来要封成 Windows 服务或者嵌入到其他框架里这个写法比裸 main 更不容易出问题。第二个习惯是定期用 MATLAB 自带的 Dependency Analyzer 检查生成的 DLL 依赖了哪些运行时组件。打包部署时被用户反馈“换个机器就起不来”绝大多数是运行时缺组件。我会在发布前把 for_testing 目录里的完整文件列表导出来对照部署机器的环境逐项勾选确认 mclmcrrt 版本一致再发版本。第三个习惯是验证 DLL 更新是否生效。MATLAB 改了一版 test.m 重新编译 DLL 后VS 工程里如果之前已经把 DLL 拷贝到外部目录容易发生“代码更新了但跑的还是旧 DLL”。我通常会把 DLL 的生成时间打印到日志里或者在初始化函数里加一句版本号输出。第一次发现这个问题是在一个反复调试边界条件的场景里改了 MATLAB 代码重新编译后 C 程序结果纹丝不动查了半天发现外部目录的 DLL 还是上一个版本从那以后我每次替换 DLL 后都强制走一遍“拷贝-验证时间戳-重跑单测”三个动作再也没被缓存问题坑过。这套链路完整走通一次之后后续再遇到 MATLAB 和 C 集成会顺手很多希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑