SolidWorks二次开发模板:COM注册与批量导出实战
简介:面向机械设计工程师与具备 C# 基础的开发者,这份 SolidWorks 二次开发模板围绕 SDK 扩展开发展开,可用于快速搭建 COM Add-in 插件、定制工具栏与窗体界面,并实现命令注册、事件处理、模型操作及外部数据交换等常见需求。压缩包共 99 个文件,约 10.24MB,包含 18 个 cs 源码、2 个 csproj 项目文件及 sln 解决方案,另有 dll、resx、bmp、xml、exe 等资源与编译产物,便于对照工程结构理解插件从开发到部署的组成。已有 730 人学习下载,适合作为入门与功能验证的起点。读者可从中获得 C# 与 SolidWorks 交互的接口调用思路、插件初始化与事件绑定范式、界面定制示例,以及错误处理、性能优化和测试部署的参考,从而减少从零搭建环境与摸索接口的成本。
1. SolidWorks 二次开发模板:把 COM 注册和接口实现这堆脏活先干完
接到一个需求:三百多个零件的自定义属性要按编码规则重写,还要按图号批量导出 PDF 和 STEP。手点一遍,三天也干不完。这时候真正卡住人的往往不是业务逻辑,而是“怎么让一个自己写的按钮出现在 SolidWorks 界面上”——COM 组件注册、CLSID、ISwAddin 接口、注册表两个位置、64 位编译目标,每一步都能让人卡半天。SolidWorks 二次开发模板解决的正是这一段:一套已经搭好骨架的 Visual Studio 解决方案,负责 Add-in 注册、连接 sldworks.exe、创建 CommandManager 工具栏按钮、接管文档事件,业务代码只需要填进预留的命令入口。适合已经会建模、想把重复操作变成一键命令的结构工程师、标准化专员,以及给企业做内部小工具的一两个人工位。
2. 模板工程拆解:从 ISwAddin 到工具栏按钮的完整链路
先看清骨架长什么样,再往里填肉。模板类资源最容易出的问题是“能编译、不能加载”,原因基本都落在注册和连接这两个环节,而不是业务代码。这一章按加载顺序拆:先看文件都在干什么,再看 SolidWorks 是怎么找到你的 DLL 的,最后落到第一个按钮。
2.1 模板目录里的文件各自负责什么
一个典型的 Add-in 模板不会只有孤零零一个 .cs,它通常包含下面这些角色。先把它们分清楚,后面改代码时才不会把注册逻辑和业务逻辑混在一起。
| 文件/目录 | 职责 | 一般改动频率 |
|---|---|---|
| SwAddin.cs | 实现 ISwAddin,管 ConnectToSW / DisconnectFromSW | 只在加功能入口时改 |
| Commands/ | 每个命令一个类,业务逻辑主要落点 | 高 |
| TaskpaneHost.cs | 任务窗格宿主,嵌 WinForms 或 WPF 面板 | 按需 |
| Registration.cs | 写/删注册表、注册 COM | 只改 GUID 和标题 |
| Properties/AssemblyInfo.cs | 程序集 GUID,与注册表必须一致 | 建项目第一件事 |
| Resources/ | 16×16、24×24 图标 | 换自己的 |
| *.csproj 生成事件 | 编译后自动 regasm 注册 | 确认位数 |
模板真正的价值在于 ConnectToSW 里的调用顺序已经被排列正确:先保存传入的 ISldWorks 和 Cookie,再创建 CommandManager 和命令组,然后订阅文档事件,最后加载任务窗格。这个顺序一旦打乱,典型症状是“按钮能出来,点一下 SolidWorks 直接消失”——因为回调触发时命令组还没激活。
2.2 加载原理:注册表、CLSID 和 ConnectToSW
SolidWorks 启动时会扫描注册表的两个位置。第一个在HKEY_LOCAL_MACHINE\SOFTWARE\SolidWorks\Addins\{GUID},子键默认值决定是否随软件启动加载(0 不加载,1 加载),另外两个字符串值是 Description 和 Title,插件列表里显示的就是它们。第二个在HKEY_CURRENT_USER\Software\SolidWorks\AddInsStartup\{GUID},DWORD 值 1 表示当前用户启用。
启动流程是:读注册表 → 拿 GUID 对应的 CLSID → CoCreateInstance 创建对象 → QueryInterface 到 ISwAddin → 调用ConnectToSW(ThisSW, Cookie)。Cookie 是 SolidWorks 分配的会话标识,后面创建 CommandManager 要原样传回去,丢了它按钮就建不出来。
模板里通常用代码写注册表,而不是发一个 .reg 文件让用户双击。等价的手写注册表长这样:
Windows Registry Editor Version 5.00 ; GUID 必须与 AssemblyInfo.cs 里的 GuidAttribute 完全一致 [HKEY_LOCAL_MACHINE\SOFTWARE\SolidWorks\Addins\{8f3c1a52-4e6b-4d0a-9c11-2b7d5e9a0c31}] @=dword:00000000 "Description"="批量属性与导出工具" "Title"="批量工具" [HKEY_CURRENT_USER\Software\SolidWorks\AddInsStartup\{8f3c1a52-4e6b-4d0a-9c11-2b7d5e9a0c31}] @=dword:00000001参数说明:HKLM 段写入需要管理员权限,所以安装包必须提权;HKCU 段决定“这台机器上当前账号是否启用”,装完默认写 1,用户才能在插件列表里勾上。GUID 换一个项目就要换一次,直接复制别人的模板而不改 GUID,两个插件会互相顶掉。
C# 里对应的注册代码通常是这个形状:
// Registration.cs [ComRegisterFunction] public static void RegisterFunction(Type t) { string guid = t.GUID.ToString("B"); // 带大括号的形式 string title = "批量工具"; using (RegistryKey addinKey = Registry.LocalMachine.CreateSubKey( @"SOFTWARE\SolidWorks\Addins\" + guid)) { addinKey.SetValue(null, 0, RegistryValueKind.DWord); // 不随启动自动加载 addinKey.SetValue("Description", "批量属性与导出工具"); addinKey.SetValue("Title", title); } using (RegistryKey startupKey = Registry.CurrentUser.CreateSubKey( @"Software\SolidWorks\AddInsStartup\" + guid)) { startupKey.SetValue(null, 1, RegistryValueKind.DWord); // 默认启用 } }[ComRegisterFunction]是 regasm 注册时会自动回调的钩子,把注册表写入放在这里,就不用额外维护安装脚本。注意这里写的是LocalMachine,普通用户跑 regasm 会直接抛权限异常,这条后面在部署章节还会再遇到。
2.3 第一个按钮:CreateCommandGroup2 的四个必填项
连接成功后,工具栏按钮靠 CommandManager 建。模板一般已经把这段封装好,但你要知道每个参数的含义,改名字、换图标、加第二个按钮都在这里。
// SwAddin.cs 中 ConnectToSW 的片段 public bool ConnectToSW(object ThisSW, int Cookie) { iSwApp = (ISldWorks)ThisSW; addinCookie = Cookie; // 用 GUID 哈希做用户 ID,避免与其他插件撞号 int userId = Math.Abs(GetType().GUID.GetHashCode()); iCmdMgr = iSwApp.GetCommandManager(addinCookie); cmdGroup = iCmdMgr.CreateCommandGroup2(userId, "批量工具", "属性批改与导出", "", -1, true); // 回调方法名必须以字符串传入,且必须是 public 实例方法 cmdGroup.AddCommandItem2("批量改属性", -1, "按规则重写自定义属性", "改属性", 0, "OnBatchProperty", "OnBatchPropertyEnable", 0, (int)swCommandItemType_e.swMenuItem | (int)swCommandItemType_e.swToolbarItem); cmdGroup.AddCommandItem2("批量导出", -1, "导出 PDF 与 STEP", "导出", 0, "OnBatchExport", "OnBatchExportEnable", 1, (int)swCommandItemType_e.swMenuItem | (int)swCommandItemType_e.swToolbarItem); cmdGroup.HasToolbar = true; cmdGroup.HasMenu = true; cmdGroup.Activate(); // 漏掉这行,按钮不会出现在任何位置 return true; }说明几个容易忽略的参数:CreateCommandGroup2的第一个参数是 UserID,同一台机器上两个插件用了相同的数字,后加载的会把前一个的命令组顶掉,所以模板用 GUID 哈希生成。第五个参数 -1 表示不指定图标索引,用默认图标;AddCommandItem2里的图标索引按资源位图顺序从 0 开始。最要紧的是Activate():它负责把命令组真正挂到 UI 上,很多“代码没错但按钮不出现”的案例就是这行被注释掉了。回调方法名写成字符串,是 SolidWorks 用反射调用的老设计,所以方法必须是 public,改成 private 后编译不报错,运行时静默失效。
3. 业务代码往哪写:文档遍历、属性批改与批量导出的四段实现
骨架跑通之后,真正费时间的是数据和边界:遍历用什么接口、属性写在哪一层、导出的文件名怎么定、以前写的 VBA 宏怎么搬进来。这一章给四段可以直接抄进模板的实现,每段都标出会翻车的参数。
3.1 遍历打开文档与特征树:别用数组接口
最简单的入口是GetDocuments(),返回当前打开的所有文档对象。要注意它返回的是 object 数组,元素是 ModelDoc2,需要逐个强转,遇到工程图或不支持的文档类型要跳过。
// 遍历当前打开的所有文档,统计零件数量 object[] docs = (object[])iSwApp.GetDocuments(); if (docs == null) return; int partCount = 0; foreach (object obj in docs) { ModelDoc2 doc = obj as ModelDoc2; if (doc == null) continue; // 少数情况会拿到空壳 if (doc.GetType() == (int)swDocumentTypes_e.swDocPART) { partCount++; FeatureManager fm = doc.FeatureManager; Feature feat = fm.GetFirstFeature(); while (feat != null) { string featName = feat.Name; // 特征名可能重名,别当唯一键 feat = feat.GetNextFeature(); } } }遍历特征用GetFirstFeature/GetNextFeature的链式调用,比GetFeatures()一次性取数组更省内存。在几百个零件、每个零件上百个特征的装配体上,这个差别会体现在卡顿上。doc.GetType()返回的是swDocumentTypes_e的整型值,别拿字符串比较文件名后缀,工程图、装配、零件的后缀在另存场景下并不总是可控。
如果要做装配体递归,还需要往下钻:
// 递归遍历装配体中的零部件 void WalkAssembly(AssemblyDoc asmDoc, string prefix) { object[] comps = (object[])asmDoc.GetComponents(false); // false = 只取顶层 if (comps == null) return; foreach (object o in comps) { Component2 comp = o as Component2; if (comp == null) continue; string path = comp.GetPathName(); // 轻化状态下可能为空 if (string.IsNullOrEmpty(path)) continue; // 轻化/压缩的零件直接跳过 if (comp.GetSuppression() != (int)swComponentSuppressionState_e.swComponentFullyResolved) continue; // 这里对 path 做属性批改,或递归进子装配 } }GetComponents(false)的参数控制是否包含子装配内部件,传 false 只取一层,自己做递归更可控。GetPathName()在轻化模式下返回空串,这是批量处理最常见的静默失败点——不报错,但一半文件没处理到。可以先iSwApp.OpenDoc6打开再处理,或者在命令入口处提示用户先解除轻化。
3.2 自定义属性的两层写法:文件级与配置特定
自定义属性分两层:文件级(整个文档一份)和配置特定(每个配置各一份)。批量改编码规则通常改文件级,做系列件参数化则必须落到配置上。模板里会把两个 CustomPropertyManager 都取出来。
// 写文件级属性 CustomPropertyManager cpm = doc.Extension.CustomPropertyManager[""]; cpm.Add3("图号", (int)swCustomInfoType_e.swCustomInfoText, "A-001", (int)swCustomPropertyAddOption_e.swCustomPropertyReplaceValue); // 遍历所有配置,写配置特定属性 string[] configs = (string[])doc.GetConfigurationNames(); foreach (string cfg in configs) { CustomPropertyManager cfgCpm = doc.Extension.CustomPropertyManager[cfg]; cfgCpm.Add3("规格", (int)swCustomInfoType_e.swCustomInfoText, "M8x30", (int)swCustomPropertyAddOption_e.swCustomPropertyReplaceValue); // 读回解析值:Get4 的第二个参数 true 表示取求值后的结果 cfgCpm.Get4("规格", false, out string rawVal, out string resolvedVal); }参数上要特别注意两点。第一,Add3的最后一个参数决定“已存在时是追加还是替换”,批量改属性必须用swCustomPropertyReplaceValue,否则同一个属性名会被写进去两条,工程图明细表直接乱掉。第二,Get4/Add3是较新版本才有的接口,老版本要退回Get2/Add2,如果团队里还混着几年前的 SolidWorks,模板里最好留一层判断分支。Get4的第二个参数传 true 时返回的是求值后的字符串(比如把 "A-001-01" 这种带链接的表达式展开),做校验时要用它,做备份时要用原始值。
改完属性要记得让文档重算,否则工程图上的链接不会更新:
doc.EditRebuild3(); // 零件/装配 // 工程图用 doc.EditRebuild() 或 doc.ForceRebuild3(false)3.3 批量导出 PDF 与 STEP:文件名和选项都在细节里
导出的核心是Extension.SaveAs,它一次性处理 PDF、STEP、IGES、DXF 等格式,靠扩展名判断。模板里通常会把路径、文件名、扩展名拼好再调用。
// 导出 PDF 与 STEP ModelDoc2 doc = iSwApp.ActiveDoc as ModelDoc2; if (doc == null) return; string pdfPath = Path.Combine(outputDir, partNo + ".pdf"); string stepPath = Path.Combine(outputDir, partNo + ".step"); int err = 0, warn = 0; // 先设 PDF 选项,必须在 SaveAs 之前 iSwApp.SetUserPreferenceIntegerValue( (int)swUserPreferenceIntegerValue_e.swPDFExportTextFont, 0); iSwApp.SetUserPreferenceToggle( (int)swUserPreferenceToggle_e.swPDFExportIncludeAllSheets, true); bool ok1 = doc.Extension.SaveAs(pdfPath, (int)swSaveAsVersion_e.swSaveAsCurrentVersion, (int)swSaveAsOptions_e.swSaveAsOptions_Silent, null, ref err, ref warn); bool ok2 = doc.Extension.SaveAs(stepPath, (int)swSaveAsVersion_e.swSaveAsCurrentVersion, (int)swSaveAsOptions_e.swSaveAsOptions_Silent, null, ref err, ref warn);SaveAs返回 false 时不要只弹一句“导出失败”,把err和warn两个位掩码打到日志里,它们能直接指出是路径非法、文件被占用还是配置不支持。PDF 的导出选项要在 SaveAs 之前设置,设置完之后不会自动恢复,批量导出前记得判断当前是全图纸还是当前图纸。工程图导出 PDF 时如果图纸有多张,swPDFExportIncludeAllSheets不打开就只导第一张,这是“文件都在但页数不对”的典型来源。
STEP 的精度和协议由swUserPreferenceIntegerValue_e.swStepAP之类的选项控制,不同版本枚举名有差异,模板里一般把这段单独抽成一个方法,方便按客户要求切换 AP203 / AP214。
3.4 VBA 宏怎么搬进模板
很多人手里的存量资产是一堆 .swp 宏。搬进 C# 插件不是逐行翻译,主要改三类东西:
| VBA 写法 | C# 写法 | 备注 |
|---|---|---|
Set swApp = Application.SldWorks | ISldWorks iSwApp = (ISldWorks)ThisSW | 插件里由 ConnectToSW 传入 |
swModel = swApp.ActiveDoc | ModelDoc2 doc = iSwApp.ActiveDoc as ModelDoc2 | 必须判空 |
Dim v As Variant | object v | 取属性时再强转 |
swModel.Extension.SelectByID2 "..." | 同名调用,out参数用ref | 签名长,建议抽封装 |
On Error Resume Next | try/catch (COMException ex) | 用 HRESULT 区分原因 |
VBA 的隐式转换在 C# 里全部失效,所以搬运时最容易出问题的是Variant参数——比如SelectByID2的坐标参数,VBA 传0能过,C# 要写0.0或(double)0,类型对不上就抛 COMException。更省事的做法是不整体迁移,把宏里成熟的逻辑用iSwApp.RunMacro2直接调用,插件只做文件遍历和参数准备,等验证稳定再逐步替换。
4. 调试与部署:把编译产物送进 sldworks.exe 的三道关
模板能在别人机器上跑,才算真正交付。这一步的三道关是:调试怎么断点、注册怎么写对、安装包怎么收尾。
4.1 附加进程调试与启动项目配置
Add-in 不是独立 exe,F5 直接跑会报“没有启动项”。标准做法有两种。第一种是在 VS 项目属性里把调试的启动外部程序指向 SolidWorks 主程序,这样按 F5 会拉起 SolidWorks,但断点可能命中不了,因为宿主进程启动早于调试器附加。更稳的是第二种:先手动开 SolidWorks,再用“调试 → 附加到进程”选中sldworks.exe。
# 生成的 DLL 在 Debug 目录下,附加进程后需重新注册才生效 "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe" ^ "D:\Work\SwAddinTemplate\bin\Debug\SwAddin.dll" /codebase /tlb/codebase是关键。不加它,regasm 只写 COM 注册不记录 DLL 的物理路径,SolidWorks 去 GAC 和系统目录找不到文件,加载就失败。/tlb会额外导出类型库,方便其他 COM 客户端调用。注意路径里带空格时整段要加引号,直接拼接会注册到错误路径上。
重新注册后必须重启 SolidWorks,因为它只在启动时扫描一次插件列表。开发期反复调试的节奏是:改代码 → 重新生成 → regasm → 关掉 SolidWorks 再打开 → 附加进程 → 点按钮。
4.2 位数、.NET 版本与平台目标
这几年前踩得最多的一条是平台目标。SolidWorks 现在只有 64 位版本,如果你的项目 Target 写成 x86,注册能成功、插件列表里能看见,勾选后直接报“无法加载”。正确配置是平台目标x64,或者AnyCPU且明确取消“首选 32 位”。
.NET Framework 版本也要对上。SolidWorks 自身跑在 .NET Framework 上,插件目标框架高于机器上已安装的版本时,同样表现为加载失败但不给出具体原因。工程里常见的配置是 .NET Framework 4.x,部署前用目标机器上实际存在的版本验证一遍,别用开发机的结果下结论。
4.3 打包分发:注册表、DLL 位置与卸载
交付给同事或客户时,不能让他们手敲 regasm。安装包里要做的事固定为三件:把 DLL 复制到固定目录(比如程序安装目录下的Addins\子目录),写 HKLM 和 HKCU 两处注册表,注册 COM 组件。
| 阶段 | 动作 | 常见问题 |
|---|---|---|
| 安装 | 复制 DLL + 依赖项 | 漏掉 Newtonsoft 之类的第三方 DLL |
| 安装 | 写 Addins 与 AddInsStartup 注册表 | 权限不足,静默失败 |
| 安装 | regasm /codebase | 用了 32 位 regasm |
| 卸载 | regasm /unregister | 残留 HKCU 项导致列表里留空条目 |
用 Inno Setup 或 WiX 都能做,核心是把 regasm 调用写成自定义动作,并且用[SystemFolder]拼出 64 位路径,不要在脚本里硬编码C:\Windows\Microsoft.NET。卸载阶段如果只删了文件没反注册,插件列表里会留下一条勾不掉的空记录,用户只能手动清注册表,体验很差。
5. 避坑与排查:插件加载链路上的五个翻车场景
这一章按“现象 → 原因 → 解决”记五条,都是我在实际项目里遇到的,按出现频率排序。
5.1 插件列表里根本看不到自己的 Add-in
现象:编译、注册都成功,SolidWorks 的插件对话框里找不到条目。
原因通常有三个。第一,注册表写到了 32 位视图:在 64 位系统上用 32 位程序写 HKLM 会落到Wow6432Node下,而 SolidWorks 读的是 64 位视图,两者不通。第二,GUID 不一致——AssemblyInfo 里换了 GUID,注册代码里还是旧的那个。第三,注册后没重启 SolidWorks,它在启动阶段扫描,运行中不会刷新。
解决:用 regedit 的 64 位视图确认HKLM\SOFTWARE\SolidWorks\Addins\{GUID}存在,用 regasm 输出里打印的 GUID 和工程里的 GuidAttribute 逐字符比对,注册完彻底退出 sldworks.exe 再启动。
5.2 按钮出现了,但是灰的,点了也没反应
现象:工具栏按钮在,图标正常,鼠标移上去是禁用状态,点击无任何输出。
原因:AddCommandItem2里传了一个 Enable 回调的名字,方法返回 0 就表示禁用,返回 1 才可点。模板默认实现常常是返回 0 等你填逻辑,忘了改就一直是灰的。另一个可能是回调名字拼错,SolidWorks 反射找不到方法,也按禁用处理。
解决:先看 Enable 回调的返回值,改成返回 1 试;再确认命令 ID 和AddCommandItem2的 UserID 一一对应,两个命令用了同一个 ID 时只有一个能响应。回调方法必须 public 且签名固定为(int cmdID)或带ref bool,签名不对同样静默失效。
5.3 文档关闭事件里抛空引用
现象:批处理跑到一半,SolidWorks 弹出未处理异常,或者直接退出。
原因:在DocumentCloseNotify这类事件里继续访问已经释放的 ModelDoc2 COM 对象。文档关掉的瞬间对象已经无效,再调它的任何方法都是空引用或 COMException。批量处理循环里同样如此——处理完一个文档不去重新取 ActiveDoc,而是继续用上一次的引用。
解决:事件回调里只做轻量动作,把耗时逻辑扔到IdleNotify或自己的队列里排空执行。循环里每处理一个文档都重新as ModelDoc2并判空,把Marshal.ReleaseComObject用在确实不再需要的对象上,不要在同一作用域里反复释放。
5.4 升级 SolidWorks 版本后编译通过、运行报 COMException
现象:老项目换到新版本机器上,编译无错,一调 API 就抛 HRESULT 异常。
原因:Interop 程序集的接口版本随主版本变化,老 DLL 引用的是旧版SolidWorks.Interop.sldworks.dll,新宿主上调到已变更的 vtable 位置;另一种是某些方法的参数从可选变成了必填。
解决:重新引用目标版本目录下的 Interop DLL 并重新生成,不要跨大版本复用 bin 目录。兼容多个版本时把调用集中在少数几个封装方法里,用反射或 dynamic 做兜底,避免 API 变更散落在几百行业务代码中。
5.5 中文路径下 SaveAs 返回 false
现象:导出循环跑到某个文件就失败,返回 false,没有弹窗。
原因:路径过长(超过 260 字符)、目录不存在、文件被 Excel 或 PDF 阅读器占用,以及个别导出器对非 ASCII 路径处理不稳定。SaveAs不会抛异常,只返回布尔值,不读err位就完全不知道错在哪。
解决:导出前统一Directory.CreateDirectory,文件名拼好后检查长度,超过就截断并加后缀;把err位掩码打到日志里;批量导出前先导出到英文临时目录再移动到目标位置,这条在路径混用中文和长图号时最省心。
6. 进阶改造:让模板从能跑变成团队能维护
模板跑通之后,真正决定它能不能长期用的是两件事:参数别硬编码、失败能回滚。
6.1 把属性规则搬到配置文件
第一版代码里最常见的写法是把“图号=代号+版本”“导出去 D:\Output”直接写死。三个人用就会打架。我一般会加一个同目录的 XML:
<Config> <Property name="图号" source="代号" pattern="{0}-{1}" /> <Export format="pdf" dir="D:\Output\PDF" /> <Export format="step" dir="D:\Output\STEP" /> </Config>读取时用Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)定位,不要在代码里写绝对路径。好处是客户改规则不用重新编译,坏处是配置错误变成运行期错误,所以读取后要做一次必填校验,缺项直接提示而不是默默用默认值。
6.2 幂等与回滚:改之前先存快照
批量改属性最怕改到一半崩了。我现在的习惯是执行前把每个文档的所有自定义属性导成一份 JSON 快照,放在输出目录的_backup子目录里,键是文件路径,值是属性名到值的字典。改崩了就写一个还原命令按快照回写。
// 快照结构示意,按文档路径存原始属性 var snapshot = new Dictionary<string, Dictionary<string, string>>(); foreach (string cfg in (string[])doc.GetConfigurationNames()) { var cpm = doc.Extension.CustomPropertyManager[cfg]; // 逐条 Get4 取原始值,写入 snapshot[path][cfg + ":" + name] }这套东西平时看着多余,出过一次事故就知道值:同事误把“材质”属性也纳入替换规则,两百多个零件的材质被清空。当时因为有快照,半小时就回滚完成。从那以后我每次跑批量写属性,都强制先出快照再执行,哪怕只改一个字段。
验证环节也别省。跑完先抽三个文件打开看属性,再检查导出目录的文件数量和大小,大小是 0 的直接挑出来重导。工具链的可靠性不是靠代码写得多漂亮,而是靠这几步固定动作。把模板当起点,把配置和快照当习惯,这套东西用两三年都不用大改。希望帮到你。
本文还有配套的精品资源,点击获取