ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MATLAB Compiler实战:从mcc打包到Runtime部署的完整指南

MATLAB Compiler实战:从mcc打包到Runtime部署的完整指南 上个月帮一位做工业检测的朋友解决了个挺实际的问题他开发了一套基于 MATLAB 的图像识别算法算法本身不复杂但真正让他头疼的是交付环节。现场工程师的电脑上既没有 MATLAB也不可能因为一个工具脚本去配正版授权和工具箱。折腾到最后方案落在了 MATLAB Compiler 上把写好的函数直接打包成独立可执行文件目标机器只需要装一个 MATLAB Runtime 就能跑。整个过程里踩了不少坑也总结出一些常规文档里不细说的经验。今天就把这套“正常用 MATLAB Compiler / Application Compiler / mcc 打包独立应用程序”的完整链路梳理一遍从原理、准备、实操到排查尽量一次讲透。这篇文章适合所有要把 MATLAB 代码交付给他人的开发者不管你是给学生做毕设工具还是给公司做内部数据处理程序或者要给客户交付商业算法模块思路基本一致。读完你不仅能独立完成打包还能知道打包后文件为什么会运行失败、怎么排查以及如何避免那些最隐蔽的坑。1. 为什么要用 MATLAB Compiler 打包独立应用1.1 没有 MATLAB 环境也能运行这是最核心的诉求在没有安装 MATLAB 的机器上.m 文件是无法直接运行的这是 MATLAB 这个封闭生态最让人头疼的一点。别人接手你的脚本后第一件事往往是装一套完整的 MATLAB 环境然后还要配好路径、装好工具箱、处理版本兼容问题非常痛苦。MATLAB Compiler 解决的就是这个问题。它并不是把 MATLAB 程序“翻译”成 C 语言或者机器码而是把 .m 函数连同其依赖的 MATLAB 运行库一起打包成一个可在目标平台运行的独立应用程序。这个独立应用运行时依赖的是一个叫 MATLAB Runtime 的运行环境你可以把它理解成“精简版 MATLAB 运行时”免费、可随你的应用一起分发而且不需要再装完整版 MATLAB。这里说的“独立”指的是不再依赖完整 MATLAB 开发环境而不是指完全不依赖任何外部文件。打包产物在目标机器上运行仍然需要先安装对应版本的 MATLAB Runtime只不过对最终用户来说它只是安装一个运行库远比安装 MATLAB 配置路径来得简单。1.2 不只是 exeCompiler 还能产出哪些交付形态很多人一提 MATLAB Compiler第一反应就是“打包成 exe”。实际上它能产出好几种交付形态不同形态对应不同使用场景交付形态常见产物适用场景独立应用程序Standalone Application.exe / Linux 可执行文件目标机器无 MATLAB直接双击或命令行调用共享库Shared LibraryC/C DLL、.NET DLL、Java Jar、Python 包在其他语言开发的系统里调用 MATLAB 算法Excel 加载项Excel 插件在 Excel 里直接调用 MATLAB 函数Hadoop/Spark 组件大数据平台算法模块部署到 Hadoop/Spark 集群比如你有一个矩阵分解算法不想重写成 Python又希望 Python 主程序能调用它那就用mcc -W python:myPkg打成一个 Python 包然后在 Python 里正常import。这一点在下文会专门演示。很多团队实际落地时并不需要 exe而是把 MATLAB 算法封装成动态库嵌入现有系统里这种方法对系统集成更友好。1.3 哪些人需要特别注意打包这件事我接触过的情况里最需要打包能力的是这几类同学第一类是算法工程师和科研人员写好了算法要给团队或客户验证效果不可能让每个人装全套 MATLAB。第二类是桌面应用开发者用 MATLAB App Designer 做了界面想以独立程序方式交付这就是 Application Compiler 最典型的使用场景。第三类是系统集成工程师需要把 MATLAB 算法嵌入到 Python、Java、C# 写的业务系统里。如果你只是自己在本地跑跑脚本不涉及交付那并不需要打包但只要你开始考虑“别人怎么用我的代码”这个问题Compiler 就会成为最高频的工具之一。2. 打包之前先把代码做一次全面“体检”很多人在打包阶段遇到的问题其实根源不在打包工具本身而是代码本身的“体质”不适合编译。MATLAB Compiler 对代码有一定的要求在点击 Package 之前先按下面四步过一遍能省掉后面大量的排查时间。2.1 入口必须是函数不能是脚本打包的第一原则你的主程序必须是一个函数而不是脚本。MATLAB 脚本是一行行顺序执行的命令在解释器环境下没问题但 Compiler 需要一个明确的函数入口来生成 main 函数。比如最简单的入口函数长这样function myApp(varargin) % 程序入口 fprintf(Hello from compiled app\n); end如果暂时还没有函数化需要把脚本改造成函数格式。改造时有一个常用的技巧脚本里引用了工作区变量改造后这些变量会成为局部变量所以在函数开头要检查变量是否存在。如果你原来的脚本依赖 base 工作区的变量编译后这些变量不会自动存在这里很容易出错。另外入口函数最好支持varargin这样生成的可执行文件才能接收外部传入的命令行参数。function main(varargin)是所有 Standalone App 的标准入口写法。2.2 资源文件要统一管理数据文件别裸奔打包时最隐蔽的坑之一就是附加资源文件。如果你的程序要读取 data.csv、模型文件、配置文件等请提前把所有这些资源放到同一个目录下并统一管理。打包时要把这些文件用-a参数或图形界面的“加附加文件”方式一起打进去否则运行时会找不到。还有一点需要注意的细节是路径别写死。很多人习惯写data load(C:\myproject\data\data.csv)这个写法在本地开发没问题但打包后的程序运行在目标机器上那个绝对路径根本不存在程序必然报错。另一个容易被忽略的问题打包后附加文件会被释放到一个由系统管理的临时目录中这个目录和 exe 所在目录并不一定相同。直接用相对路径data.csv取数据会以当前工作目录为基准寻找而目标机器上的工作目录是不可控的因此同样是隐患。正确的做法是使用ctfroot函数来定位资源文件的实际位置function main(varargin) % 获取打包后附加资源的实际释放根目录 resourceDir ctfroot; dataFile fullfile(resourceDir, data.csv); data readmatrix(dataFile); end这段代码里ctfroot是打包环境中非常重要的函数返回的是 CTF 归档文件释放后的路径。使用fullfile拼接路径可以避免不同操作系统上的分隔符问题。关于 CTF 归档的原理在排查章节还会详细展开。2.3 动态加载、路径操作和交互命令的清理Compiler 在打包时会做依赖分析它会静态扫描代码里需要哪些函数和工具箱但如果代码里有下面这些“动态”行为静态分析就比较容易漏掉使用eval、evalin、feval配合字符串变量调用函数用addpath在运行时动态添加路径用load(xxx.mat)加载数据文件而文件名是在运行时拼接出来的使用if exist(someFunction, file)之类基于函数存在性判断的逻辑这些动态行为的问题是打包工具只能根据字面内容做依赖分析如果函数名是运行时拼出来的它根本无从知道还要包含哪些文件。针对这类问题的处理思路是预先用-a参数把所有可能依赖到的文件都显式包进去或者把动态调用改为静态调用比如把feval(myfunc, x)改成switch分支直接调用myfunc(x)。另外命令行交互也要处理。如果代码里有input(请输入参数:)这种交互输入打包成 exe 后仍然可以工作但只能在命令行窗口里输入不会有额外的 GUI 输入框。如果你的用户不会用命令行最好改成通过命令行参数传入比如myApp.exe 参数1 参数2这样自动化运行也更方便。2.4 工具箱依赖的确认与 License 问题你的代码里用了哪些工具箱打包时会自动分析并纳入依赖。但这带来两个实际问题第一个问题是体积。用了 Image Processing Toolbox、Deep Learning Toolbox 这类大工具箱后打包产物体积会明显增大MATLAB Runtime 安装包也很大这是不可避免的。第二个问题是 License。打包机需要安装 Compiler 以及程序依赖的各个工具箱的许可。比如你用了 Simulink就需要 Simulink 的相关授权工具箱如果用的是试用版打包出来可能在目标机器上无法正确运行。所以打包前最好先跑一次license(test, Statistics_Toolbox)之类的命令确认授权状态正常。依赖工具箱的具体情况可以在打包完成后打开readme.txt或者用mcc输出日志查看。图方便的话也可以在命令行里用which myFunction来看这个函数究竟来自哪个工具箱。3. 两种主流打包方式图形界面与 mcc 命令行3.1 用 Application Compiler 图形界面打包推荐给新手从 MATLAB 顶部菜单选择“APP”页签找到“Application Compiler”或者在命令行输入deploytool都能打开打包界面。进入界面后核心操作分为四步第一步添加主文件。点击“Add Main File”选择你的主函数 .m 文件。如果程序有多个 .m 文件只需要选最外层入口文件因为 Compiler 会自动分析并打包所有依赖的函数。第二步添加附加文件。点击“Add Files/Directories”把需要一起打包的资源文件加进去比如 .csv、.mat、.txt 配置文件、模型权重等。这一步非常关键前面提到的数据文件读取不到多半是这一步漏掉了。第三步配置应用信息。你可以在右侧填写应用名称、公司名、版本号等元信息这些会写入最终的可执行文件属性里。同时下拉菜单里要选一个打包类型默认是 Standalone Application也可以切换成 Python Package、.NET Assembly 等。第四步点击右上角“Package”按钮。等待弹出来的打包进度跑完它会生成一个压缩包和两个文件夹。打包过程中如果代码有语法错误或者其他问题这一般是第一个暴露的时刻所以务必关注输出窗口里的 warning 和 error。打包完成后默认会生成for_redistribution、for_testing、for_redistribution_files_only三个目录。其中for_redistribution里是可安装的安装包一般会生成类似MyAppInstaller_mcr.exe的网络安装器或离线安装器for_testing里是编译好的 exe 和 ctf 文件方便你马上在当前机器上测试for_redistribution_files_only则是纯文件包不包含安装器一般用于手动分发。3.2 用 mcc 命令行打包更适合自动化构建如果你的打包操作需要频繁重复或者要集成到 CI 流水线里命令行才是正确姿势。mcc是 MATLAB 自带的标准命令在 MATLAB 命令行窗口里执行也可以在系统命令行里通过matlab -batch mcc ...执行。最常用的几个参数如下参数作用-m生成独立应用程序即 Standalone Application-W main显式指定生成可执行的 main 程序-a 文件添加附加文件/目录可重复使用-o 名称指定输出文件名-d 目录指定输出目录-R传递 Runtime 选项例如-R -logfile:log.txt-C不将 CTF 归档嵌入 exe而是单独生成 .ctf 文件-N不使用默认路径下的全部工具箱配合-p指定路径-p 路径添加指定路径一个很典型的完整打包命令是这样的mcc -m -o fitDemo -a data.csv -d ./output main.m这条命令的作用是把main.m打包成独立应用输出文件名为fitDemo把data.csv作为附加资源打包进去所有产物输出到./output目录。还有一种更正式的写法对应 Application Compiler 图形界面的全部能力mcc -m -W main -o fitDemo -a data.csv -R -logfile:run.log -d ./output main.m其中-R -logfile:run.log让程序运行时生成日志文件这个对排查运行时问题非常有帮助。如果你的代码对性能要求高也可以添加-R -singleCompThread之类的参数不过要注意是否真的需要因为这会限制多线程能力。3.3 一个完整示例从函数到可执行文件这里用一个简单的回归分析程序走一遍完整流程方便你对照。假设我们要做一个程序它读取一个 CSV 文件文件里有两列数据然后做一次一次多项式拟合输出斜率和截距。写入口函数function fitDemo(varargin) % 入口fitDemo [data.csv] % 处理输入参数 if nargin 1 ischar(varargin{1}) dataFile varargin{1}; else dataFile fullfile(ctfroot, data.csv); end % 读取数据 data readmatrix(dataFile); x data(:, 1); y data(:, 2); % 线性拟合 p polyfit(x, y, 1); slope p(1); intercept p(2); % 输出结果 fprintf(拟合结果: y %.4f * x %.4f\n, slope, intercept); end然后准备一份测试数据data.csv内容类似1,2.1 2,3.9 3,6.2 4,8.3在 MATLAB 命令行中执行打包命令mcc -m -o fitDemo -a data.csv main.m打包完成后fitDemo.exe会生成在当前目录如果想指定输出目录用-d。在本机测试时可以这样运行fitDemo.exe data.csv如果要测试附加资源是否打包正确可以带上绝对路径运行fitDemo.exe因为代码里使用了ctfroot定位data.csv即使不带参数也能找到资源并正常运行。这个案例虽然简单但已经覆盖了入口函数、输入参数、附加资源、ctfroot、命令行打包这几个最核心的知识点。3.4 打包产物解析exe、ctf 和 readme打包完成后你会看到一堆文件很多人看到这些文件会有点懵其实核心就三类第一类是 exe 本身也就是你的独立应用日常分发和运行主要靠它。正常情况下CTF 归档会嵌入这个 exe 文件内部所以单个 exe 文件携带了大部分代码和资源逻辑。但要注意它并不包含 MATLAB Runtime。第二类是 ctf 文件。CTF 是 Compiler Technology File 的缩写它本质上是一个归档文件保存了你全部 .m 文件的编码后形式以及附加资源文件的打包内容。默认情况下它会被嵌入 exe如果你用了-C参数它就会作为独立文件出现在 exe 旁边。目标机器运行时这个 ctf 文件会被解压到临时目录ctfroot就是指向这个解压目录。第三类是 readme 文档。它会列出程序依赖的 Runtime 版本、打包时的 MATLAB 版本、包含的文件清单、目标平台要求等信息。这个文件是排查运行时版本问题最重要的参考资料不要随手删掉。for_testing目录里的 exe 可以直接运行但它依赖你本地已经安装的 Runtime而for_redistribution里的安装器会自动处理 Runtime 安装更适合交付给其他用户。4. 目标机器部署MATLAB Runtime 是绕不开的一环4.1 Runtime 到底是什么为什么不能免运行时运行很多第一次打包的人都有个疑问我都打包成 exe 了为什么目标机器还要安装 Runtime这里需要澄清一下概念。MATLAB Compiler 做的是“运行时编译”它会把你的 .m 代码转换成一种中间表示最后依赖 MATLAB Runtime 里的原生库来解释和调度执行。它不是像 C/C 那样把源代码直接编译成完全脱离运行库的机器码。所以目标机器上必须存在和编译版本匹配的 MATLAB Runtime。你可以把 MATLAB Runtime 类比成 Java 的 JRE打包好的程序类似 Java 的 Jar 包JRE 就是那个公共运行环境。Java 程序不能在没有 JRE 的机器上运行MATLAB 独立应用也不能在没有 Runtime 的机器上运行。这个设计让 Compiler 能做到跨机器部署代价就是首次安装 Runtime 的体验会比较重包体也比较大。Runtime 本身是免费分发的可以随你的应用程序安装包一起提供给用户不会额外产生授权费用。4.2 Runtime 安装与静默安装参数Runtime 有很多获取方式。你在图形界面打包时可以选择让安装器自动下载 Runtime也可以到 MathWorks 官网的 Runtime 下载页面选择与打包机 MATLAB 版本完全相同的版本手动下载。安装过程本身比较简单就是一步步点击 Next。如果需要批量给很多机器部署一条静默安装命令会更高效。以 Windows 平台为例常见的静默安装方式是MyAppInstaller_mcr.exe -silent -agreeToLicense yes如果你的安装包不是这个名字而是下载的完整 Runtime一般也可以使用类似参数。更通用的做法是先运行一次安装器查看它的帮助说明因为不同年份版本的参数略有差异。在 Linux 上Runtime 解压以后通常有一个install脚本配合-mode silent -agreeToLicense yes即可完成自动安装。安装完成后Runtime 通常会注册到系统路径Windows 上会写入注册表。正常情况下 exe 运行时会自动找到 Runtime如果你把安装目录改了名或者手动移动了 Runtime就需要手动配置环境变量来指定 Runtime 路径这一步要特别小心改名的安装目录经常导致程序找不到运行库。4.3 版本匹配是一个隐蔽但高发的坑Runtime 的版本号必须和编译用的 MATLAB 版本匹配。例如你在 R2023a 中打包目标机器就要安装 R2023a 的 Runtime如果你安装了 R2022b 的 Runtime大概率会报出无法找到入口函数、加载 DLL 失败这类错误。这里的“匹配”在不同 MATLAB 版本里严格程度有差别旧版本要求完全一致新版本则要求主版本一致、Update 版本不能低于打包时的版本。保险起见还是严格遵守“同版本或更高 Update”原则最省心。判断版本最简单的方法是看打包时生成的 readme.txt。文件里会写明类似MATLAB Runtime R2023a (9.14)这样的版本信息。如果目标机器上已经安装了多个 Runtime 版本程序也可能因为加载了错误版本而报错此时可以在命令行手动执行程序观察报错信息中的 Runtime 路径来判断实际加载的是哪个版本。5. 打包后运行失败的排查与避坑实录5.1 高发问题速查表把我和身边同事踩过的坑整理成一个速查表遇到问题先对照这个表排查一轮大概率能省一半时间现象最常见原因解决方法启动直接报缺少 DLL目标机器没有安装 Runtime或版本不匹配安装匹配版本的 MATLAB Runtime报错Unable to load shared libraryRuntime 路径未找到或安装损坏重装 Runtime检查环境变量读取不到数据文件附加资源没有用-a打进包里打包时加入附加文件数据文件明明打了包还是读不到代码使用相对路径未用ctfroot改为用ctfroot拼接路径中文路径下崩溃Runtime 或程序对中文路径兼容不佳全英文路径运行杀毒软件拦截或误报编译产物被识别为未知程序加白名单或对 exe 数字签名有 Figure 界面但显示不出来没有图形环境或 JVM 被禁用确认目标机器有桌面会话检查-nojvm参数运行速度明显慢于 MATLAB 环境Runtime 首次解压或配置低首次运行后观察再优化算法逻辑5.2 数据文件读取不到的真正原因理解 ctfroot 解压机制这是打包场景里最经典的问题值得单独讲透。当你把data.csv用-a参数打进程序后这个文件并不是老老实实地躺在 exe 旁边而是被封装进了 CTF 归档里。程序运行的一瞬间Runtime 会将这个归档释放到一个临时目录并在程序结束时按需清理。这个临时目录的绝对路径就要通过ctfroot获取。所以正确读取打包资源的方式是resourcePath fullfile(ctfroot, data.csv);而不是resourcePath data.csv;相对路径的解析取决于程序的当前工作目录而工作目录在不同机器、不同启动方式下完全不同。我在实际项目中见过有人双击 exe 能成功但在命令行下运行就找不到文件的案例原因就是工作目录不同导致的。如果你希望同时支持“从外部传入文件”和“使用包内默认文件”两种模式可以参考前面示例代码的写法——有外部参数就用外部参数没有就用ctfroot拼默认文件这样兼顾灵活性和稳定性。5.3 启动慢、杀毒误报、中文路径等实战心得实际交付时启动速度是用户感知最强的部分之一。打包后的程序首次启动通常比 MATLAB 环境慢因为 Runtime 要初始化、CTF 要解压到临时目录。这个解压过程在后续运行时会因为临时目录已有缓存而变快。如果程序不需要图形界面、只做计算可以尝试在编译时加-R -nojvm这样的参数来减少 JVM 相关的初始化开销从而缩短启动时间。但要注意如果程序里有绘图、GUI、Java 相关功能就不能禁用 JVM否则相关功能会直接不可用。杀毒软件误报是另一个高频问题。MATLAB Compiler 生成的 exe 结构特殊经常被某些杀毒软件识别为未知或可疑程序。这不是代码问题但会直接影响用户体验。解决办法包括给 exe 做代码签名、向目标机器杀毒软件添加白名单、或者提前在说明文档里告知用户。如果 client 的安全策略不接受非签名程序签名这一步基本没法省。中文路径和中文用户名的兼容性在 Windows 平台上尤其值得留意。MATLAB Runtime 对纯英文路径的兼容性是最好的建议在部署文档里明确要求程序必须放在全英文路径下运行。这块在你本地开发时往往没问题但在客户现场就是噩梦因为客户的用户名可能直接是中文。5.4 代码安全编译不等于绝对加密配合 pcode 更稳最后一个不少人关心的问题打包后的代码别人能不能还原出来坦白说MATLAB Compiler 对代码的“保护”是有限的。它会把 .m 文件转换成中间表示并嵌入 CTF 归档普通用户无法直接查看源代码但 CTF 文件本身可以被逆向分析。如果算法真的非常核心需要更高强度的保护最常用的补强措施是用pcode先生成加密的 .p 文件再把 .p 文件作为输入参与打包。不过要提醒的是.p的加密也不是绝对不可破解的它只是大幅提高逆向成本让绝大多数人望而却步。对绝大多数应用场景Compiler 加 pcode 的双重保护已经足够了。如果你需要银行级、军工级的代码安全那根本不建议用任何解释型语言方案而是应该用 C/C 重写核心算法这已经不是打包工具能解决的范畴了。另外还要注意一点打包不是“一劳永逸”的。你在 R2023a 上打的包如果目标机器是 Linux那需要使用 Linux 版 MATLAB 打包如果目标是 macOS也要用 macOS 版打包。跨平台不能交叉编译这一点经常有人忽略。设计交付方案时最好一开始就明确目标平台别等打包完才发现平台不对。6. 进阶技巧把 MATLAB 算法封装成其他语言可调用的组件前面提到的都是独立应用但实际集成时很多系统根本不需要一个 exe 界面而是希望 MATLAB 算法作为一个函数被其他语言调用。这种场景下MATLAB Compiler 的共享库模式就更合适。以打包成 Python 包为例。在 Application Compiler 里把打包类型从 Standalone Application 换成 Python Package或者用命令行mcc -W python:pyFit -T link:lib -o fitDemo -a data.csv main.m打包完成后会生成一个 Python 包结构。你可以把它安装到目标机器然后像使用普通 Python 模块一样调用 MATLAB 算法import pyFit result pyFit.initialize() data_file data.csv slope, intercept pyFit.main(data_file)这里的核心是MATLAB 函数以 Python 函数的形式暴露给了 Python 环境整个调用过程对上层 Python 代码完全透明。类似的还有 .NET、Java、C 共享库的封装方式命令格式大同小异主要变化在-W参数的类型上。在开发这类集成方案时我给的建议是把 MATLAB 函数设计成纯计算、无界面的形式输入输出都用标准数据类型比如数字、字符串、数组而不要在 MATLAB 代码里弹窗、输出 GUI。这样跨语言调用时最不容易出兼容性问题。7. 绕不开的自动化与持续集成小建议如果打包是偶尔一次的工作图形界面完全够用。但如果你负责的算法会频繁更新比如每周都要给测试团队出一个新版本那就值得把mcc打包命令写进自动化脚本里。以下是我常用的一个自动化思路在 MATLAB 中写好入口函数和资源文件后用一个批处理脚本来完成全链路操作matlab -batch mcc -m -o fitDemo -a data.csv -d ./output main.m这样在持续集成服务器上只要你修改了代码并触发构建就可以自动完成打包、把for_redistribution产物上传、甚至自动发送给测试人员。整个流程不用打开 MATLAB也不用手工点击任何按钮。这样做还能顺便解决一个常见问题多人开发时不同人打包出来的结果可能因为 MATLAB 版本、工具箱路径差异而“在 A 机器上正常、在 B 机器上报错”。用统一的构建脚本和构建机器能在很大程度消除这种环境不一致带来的麻烦。我个人的体会是MATLAB Compiler 这项能力最大的价值不是把 .m 变成 .exe 这个动作本身而是彻底改变了 MATLAB 代码的交付模式。以前你交付一段算法对方要先装 MATLAB 再学怎么配路径现在你交付一个独立程序对方只需要双击运行。对于算法工程师来说这意味着你写的每一行代码都有可能真正被生产环境用起来而不仅仅停留在实验验证阶段。最后再分享一个小技巧打包好的 exe 在给外部用户之前自己一定要在一台“干净”的机器上测试一次。所谓干净就是没有安装 MATLAB、没有安装任意版本 Runtime。很多开发者在自己的电脑上测试一切正常交付到客户那里就各种问题根源往往就是开发机上有完整 MATLAB 环境掩盖了 Runtime 依赖、资源路径这些隐患。只有干净环境跑通了交付才算真正完成。
返回列表