☰
MATLAB GUI打包独立exe:Application Compiler与Runtime部署实战
2026/10/5 12:54:01 网站建设 项目流程

每次把做好的MATLAB界面发给同事,我都要先补一句:你电脑上装MATLAB了吗?没装?那你先去装个MATLAB,再装对应工具箱,再把路径配好,再把你那个.mlapp拖进运行……这一套下来,光教环境就耗掉半小时。更麻烦的是,你辛辛苦苦做好的GUI程序,交到对方手里明明只是点几个按钮、看几张图的小功能,却要让人家为一个工具去安十多个G的开发环境,这搁谁都觉得不划算。

所以很多工程师做到一定阶段都会主动研究打包,把MATLAB程序编译成不带MATLAB环境也能跑的独立exe。这事官方提供了一套挺成熟的东西,就是Application Compiler。我在R2020a和R2020b上都实测过完整流程,从GUI代码编写、依赖检查、编译打包,到拿到一台没有MATLAB的机器上部署运行,整个过程跑通之后,我最大的感受就是:这个功能真的把“开发”和“使用”彻底分开了,但这中间坑也确实不少。

这篇文章我按自己实际操作的顺序来写,从为什么选这套方案、打包前要准备什么,到逐个按钮怎么点,再到最后一份问题排查清单,尽量把每个细节都说到位。用的是2020R系列版本,其他版本界面略有差异,但核心逻辑是一样的。

1. 为什么要把GUI打包成独立exe,以及这套方案是怎么工作的

1.1 从一次“给同事发程序”说起

大概一年多前,我给测试组同事做了个信号预览GUI,功能不复杂,就是从文件夹里批量读数据文件,画个波形,能缩放能导图。当时我直接把代码发过去,让他自己跑。结果他打开MATLAB发现缺工具箱,装了半天工具箱又说License不对,折腾一上午,最后跑起来一看,路径里带中文,数据文件夹又读不到。那次之后我就下决心,以后再给人交东西,绝不给源码和MATLAB环境要求,只给一个能双击打开的exe。

不只是同事之间协作,很多场景都有这个需求:给甲方做演示工具、给实验室搭数据采集界面、给非技术背景的客户交付算法验证程序、甚至给自己保存一个“以后不用开MATLAB就能用”的版本。这时候一个独立exe的意义就很直接:收程序的人不需要知道MATLAB是什么,双击图标,界面出来,事情就结束了。

1.2 exe里面到底打包了什么:它不是一个自包含的绿色软件

这里要先说清楚一个常见误解。用Application Compiler生成的exe,并不是把所有MATLAB函数都编译成机器码塞进一个文件里,然后这台机器就什么依赖都没有了。它的真实模型是:MATLAB的编译工具把你的代码(包括GUI里的回调函数、工具箱调用)转换成一堆受保护的中间代码,再配上你用到的那些工具箱对应的动态库,最后放进一个包里。exe本身更像一个“启动器”,真正干活的计算引擎是MATLAB Runtime,也就是以前叫MCR的东西。

Runtime可以理解成一个“只运行、不开发”的MATLAB运行环境,体积不小,R2020a对应版本是9.8,安装完差不多2到3个GB。但它可以在一台机器上装好之后供多个打包程序共用,很多公司会给公用电脑装一次Runtime,之后所有同事发的exe就都能跑了。这也是为什么同样是打包,有些选项要带Runtime一起装,有些选项不带,后面实操部分我会详细说。

正因为有这层结构,你在判断“打包成功”的时候,也要多一步验证:同样的exe在你自己机器上能跑,不算数。一定要复制到一台干净、没装过MATLAB的机器上测一遍,这步才是真正的验收。

1.3 可选方案横向对比

其实想把MATLAB程序给别人用,思路不止打包exe一种,我在确定方案前也对比过几条路。

方案目标机器要求交付体积优点明显缺点
让对方装完整MATLAB需要完整License10GB以上源码透明,方便二次修改安装成本高,授权贵,看着就不专业
打包exe + 安装Runtime只需装Runtime,无需LicenseRuntime约2-3GB + exe包几十MB一次Runtime多次使用,运行速度快首次安装Runtime略麻烦
打包exe,目标机器已有Runtime只需拷贝exe和dll几十MB分发最简单要求对方机器Runtime版本与编译版本兼容
Web App Server(R2020b+)浏览器访问取决于服务器不需要安装任何东西需要一台常驻服务器,配置与运维成本高
给源码让用户自己处理需要MATLAB环境源码文件很小最省事对接收方极不友好,问题最多

我自己最后一直是“exe + Runtime”这条路。R2020b之后官方还提供Web App Server方案,能把GUI发成网页版,这适合给多人用的内部工具,但如果你只是交付给固定几个人,打包exe仍然是最省心、最可控的做法。

2. 打包前准备:90%的新手都在这一步卡住

2.1 先确认你的License和工具链是完整的

很多人打开Application Compiler发现按钮是灰的,或者打包到一半提示没有Compiler授权,问题通常出在MATLAB Compiler工具箱没装。检查方法很简单,在MATLAB命令窗口输入:

license('test', 'Compiler')

返回1就说明授权可用,返回0说明当前License不支持编译。R2020a和R2020b对MATLAB Compiler的授权逻辑基本一致,就是要确保购买或部署的License里包含这个组件。

另外一个很容易被忽略的问题是:你代码里用到的工具箱函数,编译授权也要覆盖。绝大多数打包失败不是语法错误,而是某个函数对应的工具箱虽然在开发机上能用,但该工具箱没有被Compiler支持,或者License不允许被部署。比如MATLAB Coder、Simulink这类组件,Application Compiler是不能直接处理的,后者需要的是Simulink Compiler,标题里写的“GUI程序打包”一般用不到这些,但如果你在GUI里调用了Simulink模型,就要特别注意。

2.2 学会用代码查看依赖:requiredFilesAndProducts

我强烈建议不要等到点Package了才发现缺东西。打包之前先跑一条命令,让MATLAB自己列出程序依赖的所有文件和工具箱:

[files, products] = matlab.codetools.requiredFilesAndProducts('myApp.m'); files' products

这里的myApp.m换成你的GUI主函数文件名。执行之后,files会列出你这个程序真正依赖的.m文件,products会列出依赖的工具箱及对应Matlab版本号。如果你发现某个你自己写的辅助函数没有出现在列表里,说明你的主程序里没有静态调用它,编译器很可能也不会自动带上它,这时候就要手动补。

这个命令对我们的打包思路是一个很好的体检:它告诉我程序“真正需要什么”,避免我凭印象猜。我在一次实际项目里就是用它发现GUI里动态加载的一个数据解析脚本没被列进依赖,当时要是不查,打包出来的exe到别人机器上一跑,点按钮就报“未定义函数或变量”,那场面很难收场。

2.3 把GUI工程整理成适合编译的结构

我踩过不少次坑之后,总结出一个比较保险的工程目录结构,现在每次打包前都先把代码整理成这个形态:

project_root/ ├── mainApp.m % 主函数,入口 ├── helper_funcs/ % 其他自定义函数 │ ├── loadData.m │ ├── plotWave.m │ └── exportFig.m ├── resources/ % 界面用的图标、示例数据、配置文件 │ ├── logo.png │ └── config.ini └── package_out/ % 打包输出目录,这个提前建好

如果你用的是App Designer,主程序是一个.mlapp文件,那结构也可以把.mlapp当作入口文件放到根目录,手动添加资源文件夹的时候把resources一起加进去就行。

为什么这么强调结构?因为Application Compiler虽然会自动扫描依赖,但它扫描的规则是基于主程序里的“静态引用”——也就是代码里直接写明的那些函数调用。如果代码里用了eval、feval、str2func这种动态调用,或者脚本运行时才拼接函数名,编译器是没法自动发现并打包的。把这些文件统一放在一个明确目录下,打包时手动添加整个目录,就不容易漏。

2.4 主文件必须是函数,不是脚本

这是所有新手最容易翻车的一步。Application Compiler接受的主程序,需要一个以function开头的文件。如果你习惯写脚本(从头到尾只要变量,没有函数声明),直接添加是没问题的,但编译器会提示你主程序必须是一个函数。

解决办法很简单,套一层壳。比如你的脚本叫process.m,那就新建一个run_process.m:

function run_process % 这里直接调用原本的脚本逻辑 process; end

这样打包入口就是run_process.m。

如果是App Designer设计的GUI,本身就是类定义加函数,天然满足要求。如果是GUIDE时代的老工程,主文件通常也是一对.fig和.m,打包时把.m文件作为主程序,.fig作为资源一并添加,同样能正常编译。但2020R这个时间点,GUIDE已经开始被官方标注为“不建议新项目使用”了,新项目我建议直接用App Designer。

3.Application Compiler完整打包实测过程

3.1 打开App和界面导览

在MATLAB命令窗口输入:

applicationCompiler

R2020a和R2020b会打开一个独立窗口,整个界面左侧主要功能是“打包应用程序”,对应的是Standalone Application,这正是我们要的。旁边还有“打包库”等选项,那是给其他语言调用用的,属于MATLAB Compiler SDK的范畴,先不用管。

进入“打包应用程序”页面后,界面从上到下大概是这样的结构:

  1. 主文件区:需要指定一个主程序文件,不要选多个,主程序只能有一个。
  2. 自定义打包文件区:可以手动添加额外的文件夹或文件,比如数据文件、图标文件、工具函数。
  3. 应用程序信息区:设置名称、版本号、公司名、图标、附加说明文字。
  4. Runtime包含方式:这里的问题比较关键,我单独在3.3里说。
  5. Package按钮:最终执行编译。

标题里提到“2020R版本实测”,有一点需要说明:R2020a的Application Compiler窗口相对朴素,R2020b在此基础上做了一些界面调整和Web App引导,但核心选项位置基本一致。我下面写的步骤是R2020b里实拍的逻辑,R2020a用户按相同名称找控件即可。

3.2 添加主文件与依赖文件

点“添加主文件”,选择你的GUI主函数,比如mainApp.m。如果入口是.mlapp,这里也能直接选。选完主文件后,Application Compiler会立刻做一次依赖分析,在“包含的包”或“运行时设置”里列出它自动发现的文件。

这时候回头打开2.2节里我们跑requiredFilesAndProducts的列表,对比一下自动发现的结果,有没有漏掉什么。如果漏了,就在“自定义打包文件”里手动添加。注意一个细节:添加文件夹时,编译器默认是“递归添加”,也就是把子目录里的文件也包含进去。如果你的工具函数里有一个子目录,忘了这一条,运行时还是会报找不到文件。

资源文件的添加也一样。比如GUI用到了一个图标文件logo.png,你就把它加到自定义文件里。之后在代码里引用这个资源的时候,需要按打包后实际运行的路径逻辑来写,这一块在第5节会展开,因为它是运行时最容易出问题的地方。

3.3 Runtime分发方式的选择:包含还是不包含

Application Compiler界面里有一个选项,名字类似“运行时下载”或“包含MATLAB Runtime”。它通常有几种选择:

  • 不包含:只生成exe和依赖dll,目标机器上必须已经装过同版本的MATLAB Runtime。
  • 包含Runtime安装程序:输出里会附带一个Runtime安装程序,对方可以先装Runtime再运行exe。
  • 生成完整安装包/安装器:编译器会把exe和Runtime安装程序做成一个安装向导,接收方双击后一路“下一步”即可。

选哪种取决于接收方的情况。如果接收方是一台公用机器,以后会有不少人给他发MATLAB编译程序,那第一次选“包含Runtime安装程序”装一次,以后别人再给他发exe,就可以相互用同一个Runtime了。如果接收方是完全不懂技术的业务人员,那就直接生成完整安装包,让他一路点下去,体验最友好。

有一点要提醒,R2020a和R2020b的Runtime不能混用版本支撑,比如R2020a编译的程序,对应的Runtime必须是9.8,R2020b对应9.9。不过同一台机器上可以同时装多个版本的Runtime,互不干扰。非要图省事的话,我就建议所有同学都统一用R2020b或更新版本,以后组内互相发程序也方便管理。

3.4 点Package之后到底发生了什么

上面都填好后,点“Package”,然后等待。编译过程短则两三分钟,长则十几分钟,取决于程序规模和机器性能。

打包过程中,Application Compiler会在背后调用mcc命令,把你的代码编译成中间表示,把用到的工具箱函数和资源文件统一打包成CTF归档文件,再生成exe可执行文件和一组dll。R2020a打包后通常会在输出目录里得到一个exe加一个同名的ctf文件,R2020b里这些文件通常被自动组织在输出目录的结构中。注意,这些dll和你代码里的函数库不是一回事,它们本质上是Runtime组件,缺少任何一个都会导致启动失败,所以分发时不要只拷exe,要把整个输出目录一起拷走。

打包过程中如果报错,错误信息会出现在MATLAB命令窗口。最常见的错误是“找不到编译器”,有时候是这个提示:

Error using mcc The MATLAB Compiler requires a supported compiler.

很多人一看这个就去装Visual Studio,其实在R2020a和R2020b里,用Application Compiler打包纯MATLAB代码并不需要额外安装C/C++编译器;只有你的程序里包含MEX函数或外部C代码时才需要。真遇到这个报错,先检查一下MATLAB Compiler这个工具箱本身的License是否完整,而不是浪费时间装编译器。

3.5 输出目录里那三个文件夹分别派什么用场

打包完成后,输出目录下通常会出现三个文件夹:for_testing、for_redistribution、for_redistribution_files_only。很多人第一次看到这三个文件夹就晕了。我解释一下它们各自的实际用途:

文件夹内容我的使用建议
for_testingexe、dll、ctf文件,可直接在本地运行打包后第一件事,用它做快速自测,跑通再说
for_redistribution完整的安装包(如MyAppInstaller_web.exe)交付给技术小白用户时用这个,双击安装覆盖Runtime
for_redistribution_files_only只有exe和dll,没有Runtime安装器交付给已经装了同版本Runtime的机器,拷过去就能跑

在实际交付时,我一般把for_redistribution_files_only里的东西压缩成一个zip,发给对方的同时,写清楚一条要求:“机器上已经装过MATLAB Runtime R2020a就直接解压运行;没装过就先装这里附带的Runtime安装包。”如果对方不是我这种能折腾的人,就直接发完整安装包,体验最好。

补充一个细节:for_testing目录下面有个readme.txt,我每次都会打开看一眼,里面写了这个程序依赖的Runtime版本和运行注意事项。这比自己记靠谱得多,建议你也养成习惯。

3.6 不想开界面?命令行mcc一条龙

Application Compiler毕竟是图形界面,手动点几个按钮能满足大多数场景。如果你的打包流程要走自动化,或者要把打包步骤写进CI脚本,那就得用命令行方式。以我的一个工具为例,主程序是mainApp.m,我想打包成一个叫MyTool的独立exe,命令是:

mcc -e -m -o MyTool -W main:MyTool -T link:exe mainApp.m helper_funcs/loadData.m resources/logo.png

参数拆开解释一下:

  • -e:生成独立应用程序,不需要接收方装MATLAB。
  • -m:告诉编译器生成的类型是“独立应用”而非库。
  • -o MyTool:指定输出文件名。
  • -W main:MyTool:设置exe入口主函数名。
  • -T link:exe:指定目标类型为exe可执行文件。

命令行方式和界面方式的底层一致,但命令行方式不会自动帮你把“自定义文件”里的非代码文件(如图片、数据文件)编排好,需要你手动把资源文件加到参数里。所以如果工程里有大量资源文件,我还是更推荐用图形界面勾选,省得命令行里一长串路径看着头晕。

4. 把exe搬到没有MATLAB的机器上

4.1 完整部署流程:装Runtime再跑exe

打包完成后,找一台干净机器做验收。这台机器最好是一台没有装过任何MATLAB组件的Windows,或者虚拟机也行。部署步骤是这样:

  1. 从for_redistribution目录里拿到完整安装包,比如MyAppInstaller_web.exe,双击运行。
  2. 安装程序会先安装MATLAB Runtime,按提示选择安装路径,默认路径就好,不建议改到带空格或中文的目录。
  3. 安装完成后再把exe和dll文件拷贝到某个固定目录,双击exe运行。

如果你手里是for_redistribution_files_only,那就跳过安装步骤,前提是机器上已经有R2020a(Runtime 9.8)或R2020b(Runtime 9.9)。判断机器装没装Runtime,最简单的办法是看“控制面板-程序和功能”,搜索“MATLAB Runtime”,看到对应版本号就说明有。

一个很容易踩的误区是:把exe拷贝过去了,但和它同目录的那几个dll没有一起拷,或者只拷了exe而忘记了旁边的ctf文件。结果双击后程序闪退,毫无提示。所以我一再强调,拷贝时要拷整个for_redistribution_files_only目录里的内容,而不是只挑一个exe文件。

4.2 在目标机器上的GUI验收清单

部署到干净机器上,不要只双击一次发现能开界面就算完。GUI程序的验收要多测几个关键点,我自己习惯按这个清单来:

  • 双击exe,界面能否正常打开,是否有报错弹窗。
  • 触发几个主要按钮功能,尤其是涉及读文件、写文件、调用外部程序的回调。
  • 切换不同工作目录再运行,看看程序是否依赖“exe所在目录”才能找到数据文件。
  • 看看任务管理器里进程是否正常退出,不留下残留进程。

其中第三点最常见。很多程序在开发机里直接在代码里写相对路径,比如readtable('data.csv'),在开发机上跑没问题,因为当年当前目录就是工程目录。但打包后双击exe,当前工作目录通常是exe所在目录或系统用户目录,如果程序恰好在启动时读配置文件,就会找错路径。在验收清单里加上这一项,能提前发现问题。

4.3 Runtime版本冲突与多版本共存

一台机器上如果先后装过R2019b、R2020a、R2023b的Runtime,它们会各自独立安装,互不影响。MATLAB Runtime的安装机制做得还行,不会互相覆盖。但有一个麻烦点是:如果你编译用的是R2020a,而目标机器只有R2020b的Runtime,程序是跑不起来的。Runtime版本和编译版本必须严格对应,至少对R2020a和R2020b是这样。

所以你在给多个同事分发程序时,最好在说明文档里写清楚:“本程序基于MATLAB R2020a编译,需要安装MATLAB Runtime 9.8。”如果不确定对方是否装过,可以直接让他安装包里的Runtime安装程序走一遍。不用担心重复安装,Runtime如果已经存在,安装程序会提示修复或直接跳过,不会把系统搞乱。

5. 打包与部署阶段最常见的坑,以及对应的排查方法

5.1 路径问题:不是代码的问题,是运行时的工作目录变了

这个问题压到第一个说,因为它影响面太大。打包后的程序和开发机上的MATLAB环境有个本质区别:开发时,你的当前工作目录是工程目录,能直接访问相对路径;打包后,程序没有“当前脚本目录”这一说,它的工作目录取决于你怎么启动它。

如果代码里用pwd来定位资源文件,打包后十有八九会翻车。正确的做法是,编译程序内需要引用的资源文件,要在代码里用ctfroot获取打包归档的解压目录:

resPath = fullfile(ctfroot, 'logo.png');

ctfroot是打包后程序运行时,MATLAB Runtime把CTF归档内容解压到的临时目录。代码里通过它就能稳定找到打包进去的资源。如果你想定位的是“用户当前在某些GUI里选中的文件”,那应该用uigetfile返回的路径,而不是猜测相对路径。

还要小心一点:不要往ctfroot目录里写文件。这个目录对程序来说是只读的,每次启动都会临时解压,退出后可能清理掉。需要写日志、写配置、写结果的时候,应该写到pwd或用户文档目录。

5.2 动态函数调用导致的“运行时报错,编译时一切正常”

Application Compiler的依赖分析是静态的。如果你的代码里写了:

funcName = 'myFunc'; feval(funcName, data);

或者用str2func拼接回调函数名,编译器在打包时根本看不到myFunc这个名字,更不会主动把它加进去。结果就是编译顺利通过,但你到目标机器上一跑,点某个按钮就报“未定义函数或变量'myFunc'”。

排查方法:检查代码里所有eval、feval、str2func、evalin调用,凡是动态调用的函数,都要在打包时手动添加到自定义文件里,或者改用静态调用写法:

myFunc(data); % 直接调用,编译器能识别

实在要动态调用,就在代码里加一行“假引用”骗过依赖分析,比如在文件开头写上:

if false myFunc(); end

这样编译器会把myFunc包含进去,且运行时不会真正执行。这个技巧不优雅但好用,我把它留在了很多老项目的代码里。

5.3 杀毒软件误报与Windows SmartScreen拦截

MATLAB编译出来的exe启动时会释放dll、读注册表、链接Runtime,行为和很多商业软件的首次启动差不多,部分杀毒软件或者Windows SmartScreen会对它产生“不认识”的提示。我在Windows 10和Windows 11上都遇到过“Windows已保护你的电脑”弹窗,需要点“更多信息-仍要运行”才能继续。

如果你的接收方是业务部门,这种弹窗会非常有杀伤力,对方可能直接觉得程序有问题不敢点。解决方法是把生成的安装包做代码签名,但个人开发者搞到代码签名证书成本不低。折中方案是提前告知接收方,这是MATLAB Runtime的常见提示,让他放心点“仍要运行”。另外,在公司内部杀毒软件的管理后台,可以把exe所在目录加白名单,但这通常需要IT权限,我一般只在文档里提示一下。

5.4 常见问题速查表

现象原因解决办法
打包时提示缺少编译器MATLAB Compiler授权不完整,或代码里含MEX文件检查License;若有MEX则安装对应C编译器
双击exe闪退,任务管理器一闪而过缺少dll或ctf文件,或Runtime版本不匹配从for_redistribution_files_only整个目录拷贝;确认Runtime版本
界面能打开,点按钮报“未定义函数”动态调用函数没被打包手动添加文件或用静态调用写法
读取不到配置文件或图片使用了相对路径改用ctfroot拼路径
程序第一次启动慢Runtime首次解压CTF归档属于正常现象,后续启动会加快
杀毒软件拦截Runtime行为类似商业软件提示接收方放行,或做代码签名
中文路径下运行异常部分老代码对中文路径支持差建议安装路径和数据路径都避免中文

6. 打包之后的日常维护与升级建议

打包不是一次性工作,程序后续要改Bug、加功能,就会涉及重新编译和重新分发。这里有几个经验值得说说。

第一,建议给每次打包留下“编译记录”。我在工程目录里放了一个build_log.txt,每次打包都记一下编译时间、MATLAB版本、Runtime版本、输出目录大小、测试结果。这样过两个月用户报了一个老版本的Bug,我能快速知道那个版本是用什么环境打的,能复现还是不能复现,心里有数。

第二,升级MATLAB主版本时,要重新编译一遍所有已交付的程序。不要把R2020a编译的exe指望到R2023b的Runtime上跑,版本对不上绝对跑不了。像从R2020a升到R2020b这种小版本升级,也建议重新编译一次,旧版Runtime虽然还能装,但总归多维护一个版本不是好事。

第三,新项目建议直接用App Designer而不是GUIDE。GUIDE在R2020a里还能正常用,但官方已经逐步淘汰它,新版本MATLAB里已经很难找到完整支持。App Designer做的.mlapp不仅能正常被Application Compiler打包,界面风格也更现代,对高分屏支持也更好。

我自己的习惯是:每次代码改动后,先在开发机上用for_testing里的exe跑一遍;确认没问题,再重新生成for_redistribution安装包;最后把新包发给测试的同事,并请他确认是不是干净机器上测试的。这套流程看着多,但能避免绝大多数“在我电脑上明明好的”这种尴尬。

最后再分享一个小技巧:打包完的exe不要直接放在桌面或临时文件夹里就给人家,压缩成一个zip再发给对方,zip里放一个README.txt,把Runtime版本要求、首次运行注意事项写清楚。接收方体验会差很多,你后续被问的问题也少很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询