
简介面向机械设计工程师与具备 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 和 ConnectToSWSolidWorks 启动时会扫描注册表的两个位置。第一个在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.SldWorksISldWorks iSwApp (ISldWorks)ThisSW插件里由 ConnectToSW 传入swModel swApp.ActiveDocModelDoc2 doc iSwApp.ActiveDoc as ModelDoc2必须判空Dim v As Variantobject v取属性时再强转swModel.Extension.SelectByID2 ...同名调用out参数用ref签名长建议抽封装On Error Resume Nexttry/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 不是独立 exeF5 直接跑会报“没有启动项”。标准做法有两种。第一种是在 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”直接写死。三个人用就会打架。我一般会加一个同目录的 XMLConfig Property name图号 source代号 pattern{0}-{1} / Export formatpdf dirD:\Output\PDF / Export formatstep dirD:\Output\STEP / /Config读取时用Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)定位不要在代码里写绝对路径。好处是客户改规则不用重新编译坏处是配置错误变成运行期错误所以读取后要做一次必填校验缺项直接提示而不是默默用默认值。6.2 幂等与回滚改之前先存快照批量改属性最怕改到一半崩了。我现在的习惯是执行前把每个文档的所有自定义属性导成一份 JSON 快照放在输出目录的_backup子目录里键是文件路径值是属性名到值的字典。改崩了就写一个还原命令按快照回写。// 快照结构示意按文档路径存原始属性 var snapshot new Dictionarystring, Dictionarystring, string(); foreach (string cfg in (string[])doc.GetConfigurationNames()) { var cpm doc.Extension.CustomPropertyManager[cfg]; // 逐条 Get4 取原始值写入 snapshot[path][cfg : name] }这套东西平时看着多余出过一次事故就知道值同事误把“材质”属性也纳入替换规则两百多个零件的材质被清空。当时因为有快照半小时就回滚完成。从那以后我每次跑批量写属性都强制先出快照再执行哪怕只改一个字段。验证环节也别省。跑完先抽三个文件打开看属性再检查导出目录的文件数量和大小大小是 0 的直接挑出来重导。工具链的可靠性不是靠代码写得多漂亮而是靠这几步固定动作。把模板当起点把配置和快照当习惯这套东西用两三年都不用大改。希望帮到你。本文还有配套的精品资源点击获取