SolidWorks二次开发模板:COM连接、批量出图与避坑骨架
发布时间:2026/11/2 11:23:13 锦皓数字建站

简介SolidWorks二次开发模板是一套面向机械设计人员与C#开发者的SDK入门工程帮助读者在SolidWorks环境中快速搭建COM Add-in插件、定制菜单工具栏并实现命令与事件响应。压缩包共99个文件约10.24MB以cs源码、dll类库、csproj与sln工程文件为主辅以resx/resources界面资源、bmp图标、xml与txt配置说明结构上包含SwCSharpAddin1插件项目、Windows Forms界面以及属性页处理等模块。已有730人学习下载适合作为二次开发练手与项目脚手架。模板覆盖插件初始化、命令注册、事件处理、界面定制和SolidWorks操作封装等典型环节读者可据此理解ISldWorks、ISwCAddin等接口的调用方式并在此基础上扩展建模、文件操作或数据交换功能减少从零搭建工程与调试接口的时间。1. SolidWorks 二次开发模板不是教程而是已经处理好 COM 常见问题的项目骨架SolidWorks 二次开发模板不是教你建模的教程它是一套已经把 COM 连接、文档打开关闭、自定义属性读写、工程图批量出图这几件事封装好的项目骨架。我最早接批量改图号的活一百多张图纸手工改到凌晨改到第十几张还把两个图号弄反了那次之后才开始认真拆这类模板。它适合两类人VBA 宏写到几百行开始莫名其妙崩的以及想把宏升级成能装到别人机器上的插件的。下面按骨架里有什么、连接层怎么管、工具层怎么改、哪里最容易出问题这个顺序拆。2. 骨架里装了什么三条技术路线与四层目录拿到一份二次开发模板第一件事不是打开 Program.cs 看代码而是先判它走的是哪条技术路线。路线选错后面写多少业务代码都是白费一个只做一次性批量导出的活硬做成 Add-in 插件光注册表就能折腾两天反过来一个要给全组十几个人天天点的工具做成控制台 exe每次都得让人先开 SolidWorks 再双击用不了一周就没人用了。这一章先把三条路线的边界划清楚再看模板为什么这么分层。2.1 三条路线的取舍宏、插件、外部进程形态入口部署成本适合的活模板里的角色VBA 宏 .swpSolidWorks 内部运行拷文件即可一次性脚本、录制动作改出来的小工具只留参考实现不进主工程.NET Add-in随 SolidWorks 启动加载RegAsm 注册 两处注册表团队常用工具、需要菜单或任务面板独立 Host 工程共用业务类库外部进程 exe双击或计划任务拷目录 运行时批量无人值守、和别的系统对接主骨架VBA 宏的优势是零部署录一遍操作就能改缺点也很明显没有像样的调试器版本管理基本靠改文件名加日期业务一复杂就是几千行的单文件谁接手谁骂。它适合验证「这个操作 API 到底能不能做」不适合长期维护。Add-in 的优势是用户无感打开 SolidWorks 菜单就在那儿。代价是部署链最长程序集要 RegAsm 注册 COM注册表里要写插件描述和启动项32 位和 64 位注册表还得分清。我见过最常见的情况是开发机上跑得好好的换台机器菜单就是不出现最后发现是注册到了 32 位视图里。外部进程 exe 是我现在默认选的形态。它通过 COM 连到 SolidWorks不依赖插件加载机制日志、配置、异常都能自己掌控还能挂到计划任务上半夜跑。它要额外处理两件事SolidWorks 已经开着的时候要连上去而不是再起一个以及批量跑完要把进程收拾干净。这两件事正好是模板连接层解决的问题。模板的取舍是主骨架用外部进程业务代码全部放在一个独立的类库里Add-in 只做一层薄壳去调同一个类库。这样同一个功能白天能用插件点晚上能用命令行批量跑业务逻辑只写一份。2.2 四层目录按改动频率切不按技术名词切看一个模板好不好用我一般先看它的目录是怎么切的。按 Models、Services、Helpers 这种技术名词切的业务一多就会散按改动频率切的接手的人一眼知道哪儿能动手。这套骨架大致是这么分的目录职责一年改几次Connection连接、可见性、文档开关、COM 释放一两次Utils单位换算、选择、属性读写、特征遍历三五次Jobs一个类一个活批量出图、BOM 导出、属性同步几乎每周Host控制台入口或 Add-in 入口只做参数解析和日志初始化一两次Config路径、模板名、图纸格式、日志级别每次上线前这么切的好处很直接改业务不动连接层就不需要重新验证「进程关不关得掉」这类底层问题。反过来如果连接代码和业务代码混在一个文件里某天你只是想加一个自定义属性却要顺带重测整条链路这就是给自己找麻烦。2.3 环境准备与第一次跑通前置条件只有三条但每条都能卡住人。第一机器上必须装 SolidWorks因为 .NET 工程要引用的两个 Interop 程序集就来自安装目录下的 api\redist 子目录具体后缀各版本不一样按你本机的实际路径来。第二工程平台目标要设成 x64SolidWorks 是 64 位进程AnyCPU 在某些宿主里会以 32 位启动连上去直接报类未注册。第三引用的两个程序集把「嵌入互操作类型」关掉否则编译器会生成一份内嵌的类型副本跨程序集传 ISldWorks 的时候两边类型对不上报的错还特别绕。// Connection/SldWorksSession.cs using SolidWorks.Interop.sldworks; using System; using System.Runtime.InteropServices; public sealed class SldWorksSession : IDisposable { public ISldWorks App { get; private set; } private readonly bool _owned; // 实例是不是我们自己起的决定 Dispose 时敢不敢动它 public SldWorksSession(bool startIfMissing true, bool visible true) { App TryGetRunning(); _owned App null; if (_owned) { if (!startIfMissing) throw new InvalidOperationException(没有找到正在运行的实例且不允许新建); // ProgID 是唯一稳定的入口别去拼注册表路径 var t Type.GetTypeFromProgID(SldWorks.Application); if (t null) throw new InvalidOperationException(本机未注册检查是否安装了对应版本); App (ISldWorks)Activator.CreateInstance(t); } App.Visible visible; // 无人值守时设 false批量出图能快一截 App.CommandInProgress true; // 挡住用户中途点界面造成的重入 } private static ISldWorks TryGetRunning() { try { return (ISldWorks)Marshal.GetActiveObject(SldWorks.Application); } catch (COMException) { return null; } // 没开着就是没开着不当错误往上抛 } public void Dispose() { App.CommandInProgress false; // 只关自己起的实例用户开着的那个不许动 if (_owned) { try { App.ExitApp(); } catch { } } Com.Release(App); App null; } }这段代码有三个参数值得说清楚。startIfMissing 控制「没有实例时要不要自己起一个」批量任务一般设 true而挂在别人机器上跟着界面走的小工具要设 false避免用户以为只是打开一个工具结果后台多出一个看不见的进程。visible 直接决定性能隐藏窗口跑批量比可见快但代价是出错时看不到任何提示只能靠日志。CommandInProgress 是最容易漏的一个它挡住用户在跑批过程中点界面能省掉一大半「跑着跑着就不动了」的报障。需要留意的是 Marshal.GetActiveObject 只在 .NET Framework 上现成可用更新的运行时里这个方法被拿掉了得自己写 P/Invoke 或者干脆改走 Add-in 形态。这是选路线时的硬约束不是写法问题。3. 连接层把 ISldWorks 拿到手再干净地还回去业务代码写错最多是结果不对连接层写错是进程泄漏、文件锁死、下一批任务直接打不开文档。这部分代码量最少但值得反复看。我一般的判断标准是跑完一千个文件任务管理器里不该多出任何一个残留进程工程目录下不该多出任何 .lck 或者临时文件。3.1 早绑定与晚绑定性能之外还有部署问题早绑定就是引用 Interop 程序集编译期就能检查方法名和参数类型调用走的是 vtable速度快代码也有智能提示。代价是编译机必须能找到这两个程序集而且换了 SolidWorks 大版本、引用路径变了就得重新引一次。有些人图省事把程序集拷进工程目录过半年自己都忘了这份是从哪个版本拷出来的编译能过、运行报类型转换失败排查起来极其难受。晚绑定是用 dynamic 或者 Type.InvokeMember运行时按名字找方法不需要引用代价是所有错误都推迟到运行时才暴露拼错一个字母也得跑一遍才知道。它真正的价值在于「同一份程序要在多个版本上跑」这时候用早绑定就得为每个版本编一份。模板的做法是早绑定加一层隔离业务层不直接接触 ISldWorks所有调用经过一个接口包装。这样以后真要切晚绑定只动一个文件业务代码一行不改。3.2 打开、关闭、退出三种语义别混着用先看代码注释里写了每个地方为什么这么写。public IModelDoc2 Open(string path, bool silent true) { // 按文件内容判类型别根据扩展名猜.sldprt 也可能被存成别的 int docType App.GetDocumentType(path); if (docType 0) throw new IOException($无法识别文档类型{path}); int errors 0, warnings 0; int options silent ? (int)swOpenDocOptions_e.swOpenDocOptions_Silent : 0; var doc App.OpenDoc6(path, docType, options, , ref errors, ref warnings); if (doc null) throw new IOException($打开失败{Path.GetFileName(path)} errors{errors} warnings{warnings}); return doc; } public void Close(IModelDoc2 doc) { if (doc null) return; // CloseDoc 认的是标题不是路径同名文档并存时会关错先取标题再关 string title doc.GetTitle(); try { App.CloseDoc(title); } catch (COMException) { } }OpenDoc6 的两个 ref 参数是排查的唯一线索成功的时候它们也有值比如警告里会告诉你引用的子装配找不到。我习惯把 errors 和 warnings 连同文件名一起写进日志出问题的时候能直接定位是哪个引用断了而不是只知道「有一批打不开」。swOpenDocOptions_Silent 抑制的是打开过程中的提示框比如缺失引用、轻化、大型装配模式的选择。批量场景必须开否则跑到某个缺零件的装配就弹个框卡在那里等人点半夜没人点任务就一直挂着。CloseDoc 的参数是文档标题不是完整路径这是个很常见的坑。SolidWorks 允许多开同名文档来自不同目录这时候按标题关就会关错。稳一点的做法是关之前判断 App.GetDocumentCount 或者干脆在批量任务里保证一次只开一个文档。退出语义有三种别混只关文档、退出应用、只释放引用不管进程。模板的规矩是「谁开的谁负责关」Session 里记了 _owned 标志只关自己起的实例。这条规矩能避免一个非常烦人的投诉用户开着 SolidWorks 做设计你跑个工具把人家的界面关了。3.3 COM 引用计数为什么你的进程关不掉这是二次开发里最经典的一类问题。把变量赋成 null 不等于释放 COM 对象运行时可调用包装器RCW还替你持着一个引用引用计数没归零进程就一直挂着。更麻烦的是遍历的时候每个 Feature、每个 Component 都是一个新的 RCW漏一个就漏一个。// Utils/Com.cs public static class Com { public static void Release(params object[] objs) { foreach (var o in objs) { if (o ! null Marshal.IsComObject(o)) { // 已经释放过的再放会抛异常这里吞掉是合理的 try { Marshal.ReleaseComObject(o); } catch (InvalidComObjectException) { } } } } // 只在进程级收尾时调一次 GC 几十毫秒放循环里跑几千次就是几分钟 public static void FullCleanup() { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } }释放顺序要跟获取顺序反过来先放文档里的特征、视图、注释再放文档最后放 App。顺序反了最轻是抛异常重一点是某个对象还挂在已释放的父对象上后面再访问直接崩在不确定的位置。FullCleanup 千万别放进循环。我见过有人为了「确保不泄漏」每处理一个文件就调一次一千个文件跑下来多花七八分钟还以为是 SolidWorks 本身慢。正确的做法是整个批次结束、进程退出前调一次就够。3.4 事件订阅回调线程和重入事件用 C# 的 写法最省事底层是连接点编译器帮你生成了包装。但有两个约束得记住回调在 SolidWorks 的主线程上执行你在里面开 Task 去调 API 是无效的会撞上 RPC_E_CALL_REJECTED回调里也别弹模态框一弹整个 SolidWorks 就等在那儿。// 事件必须成对解绑否则退出时对象被事件源持着进程收不掉 App.ActiveModelDocChangeNotify OnDocChanged; App.DocumentSaveNotify OnDocSaved; private int OnDocChanged() { // 返回值必须是 0非 0 会打断 SolidWorks 自己的流程 _log.Info($活动文档切换{App.ActiveDoc?.GetTitle()}); return 0; }返回值这点很多人不知道看到签名返回 int 就随便 return 了结果 SolidWorks 行为变得很怪成了难查的黑匣子。事件处理函数一律 return 0除非你确实清楚自己在打断什么流程。4. 工具层单位、选择、属性、出图四处最常改的代码工具层是业务代码真正落地的地方。一个批量出图任务表面上只有「打开、改属性、导出、关闭」四步实际写起来八成的时间花在坐标单位不对选不中实体、属性改名没覆盖、导出参数没设对只出了一张图纸。这一章把四块最常改的代码拆开每块都给出可抄的写法和对应的参数解释。4.1 单位内部只认米界面显示毫米API 里所有长度入参出参都是米界面上看到的是毫米这中间的换算错了不会报错只会「选不中东西」。这是最消耗时间的一类 bug代码没抛异常日志也没错误就是 SelectByID2 一直返回 false。public static class Units { public const double MmPerMeter 1000.0; public static double MmToM(double mm) mm / MmPerMeter; // 表格里的 mm 进 API 前必须换 public static double MToMm(double m) m * MmPerMeter; // API 吐出来的坐标显示给人看时换 // 只用来打日志和做校验不参与几何运算 public static string Describe(IModelDoc2 doc) { // 用 GetUnits 拿单位制别去比对字符串MM 在本地化版本里会变成别的 return ((swUnitSystem_e)doc.GetUnits()).ToString(); } }除了坐标还有两处容易忘工程图里图纸尺寸、视图位置、注释坐标都是米装配里配合的偏移值也是米。从 Excel 读配置表的时候在边界处统一换一次别让毫米值往业务层深处流流到第三层就没人记得这一列到底是毫米还是米。顺便说一个批量场景的必设项把「新建尺寸时弹出输入框」关掉。加尺寸、改尺寸的过程中弹一次框无人值守就死在那儿了。这个开关是用户偏好里的 Toggle会话初始化时设一次即可具体枚举名按你本机版本的 Interop 来查别硬背。4.2 选择与遍历SelectByID2 的几个隐藏参数SelectByID2 的参数表看起来长实际常用的就三个坐标米、Append是否累加选择、Mark标记值。Mark 在装配配合、批量选面的时候特别有用用不同的标记值把「要配合的面」和「参考面」分开后面按标记筛选比按顺序取稳得多。它返回 false 不抛异常所以每个调用点都要判。排查顺序建议固定成三步先确认坐标单位再确认目标实体没被压缩或隐藏最后确认当前文档是活动文档不是的话先激活。public static IEnumerableFeature WalkFeatures(IModelDoc2 doc) { var feat doc.FirstFeature() as Feature; while (feat ! null) { yield return feat; // 子特征递归模板里限 8 层复杂装配里无限递归会把栈打爆 var sub feat.GetFirstSubFeature() as Feature; int depth 0; while (sub ! null depth 8) { yield return sub; sub sub.GetNextSubFeature() as Feature; } feat feat.GetNextFeature() as Feature; } }遍历用迭代器写起来最舒服但有个副作用得清楚yield 出去的对象被调用方持着枚举过程中别去 CloseDoc 或者切活动文档否则下一次 MoveNext 访问的就是已经失效的对象。要么先 ToList 把需要的信息摘出来要么在枚举期间保证文档不变。4.3 自定义属性Add3 会静默失败Get4 有两个返回值属性这块的坑集中在两个方法上。Add3 如果同名属性已存在而你没传覆盖选项它不报错也不改值你的批量更新就这么静默地什么都没干。Get4 有两个 out 参数一个是原始文本一个是解析后的值图纸里用 $PRP 链接的必须看后者。public static void SetProp(IModelDoc2 doc, string name, string value, string config ) { // config 传空串表示文件级属性传配置名表示配置特定属性 var cpm doc.Extension.CustomPropertyManager[config]; cpm.Add3(name, (int)swCustomInfoType_e.swCustomInfoText, value, (int)swCustomPropertyAddOption_e.swCustomPropertyReplaceValue); // 这个选项才覆盖 } public static string GetProp(IModelDoc2 doc, string name, string config ) { var cpm doc.Extension.CustomPropertyManager[config]; string raw, resolved; cpm.Get4(name, false, out raw, out resolved); // false 不解析 $PRP 引用 return resolved; // 链接求值后的结果才等于图纸上看到的 }文件级属性和配置特定属性的区别在出图场景里影响很大。同一个零件多个配置图号如果放在文件级所有配置的图纸都会印同一个图号放配置特定才能一配置一图号。改完属性必须重建一次图纸里的 $PRP 链接才会刷新不然导出的 PDF 上还是旧图号而且这个过程不报错。4.4 工程图批量出图图号替换 PDF 导出这块是整套模板里最实用的部分也是坑最密集的部分。核心流程是读配置表拿到「文件路径 → 图号」的对应关系逐个打开、写属性、重建、导出、关闭。中间任何一步失败都不该中断整批所以每个文件单独 try/catch失败的记进待重跑清单。public void RunBatch(IEnumerable(string Path, string DrawingNo) jobs, string outDir) { Directory.CreateDirectory(outDir); var failed new Liststring(); foreach (var job in jobs) { IModelDoc2 doc null; try { doc _session.Open(job.Path, silent: true); // 1) 先改属性再重建顺序反了图纸上还是旧值 Tools.SetProp(doc, 图号, job.DrawingNo); doc.ForceRebuild3(false); // 2) 导出。ExportData 传 null 表示沿用上一次的 PDF 打印设置 string target Path.Combine(outDir, Path.GetFileNameWithoutExtension(job.Path) .pdf); int errors 0, warnings 0; bool ok doc.Extension.SaveAs(target, (int)swSaveAsVersion_e.swSaveAsCurrentVersion, (int)swSaveAsOptions_e.swSaveAsOptions_Silent, null, ref errors, ref warnings); if (!ok) throw new IOException($导出失败 errors{errors} warnings{warnings}); } catch (Exception ex) { failed.Add(${job.Path}\t{ex.Message}); _log.Error(job.Path, ex); } finally { _session.Close(doc); // 关文档不保存原始文件保持干净 } } File.WriteAllLines(Path.Combine(outDir, failed.txt), failed); }ExportData 传 null 这一条要特别说明。它沿用上一次的 PDF 导出设置包括图纸范围当前图纸还是所有图纸、线宽、是否黑白。这意味着在一台新机器上第一次跑可能每份 PDF 只出了一张图纸而且不报错。稳妥的做法是先手工导一次把设置定下来或者构造导出数据对象显式指定范围。ForceRebuild3 的参数是「是否强制重建所有配置」批量出图一般传 false只重建当前配置。传 true 在大装配上能把时间翻好几倍而图纸上的引用其实只需要当前配置更新。finally 里关文档而不是保存是刻意的原始文件不该被批量任务改动属性变更只存在于内存中。如果业务确实需要把图号写回文件另开一个任务专门做保存别混在出图流程里否则中途失败会留下改了一半的文件。5. 避坑与排查六条出问题记录下面这些是按「现象、原因、解决」整理的都是我在实际项目里遇上过的。新手照着手册写代码通常不会碰上全部但只要做过两个真实项目至少会撞上其中三条。5.1 编译期与引用期第一条现象是编译报「找不到类型 ISldWorks」或者两个程序集版本冲突。原因通常是引用的 Interop 程序集来自另一台机器或者另一个大版本的安装目录也可能是不小心把「嵌入互操作类型」打开了编译器内嵌了一份类型副本跨程序集传递时两边类型被认为不同报错信息还很绕。解决方式很固定直接引用本机安装目录下的那两个程序集嵌入互操作类型设为 false复制到本地设为 false让运行时用安装目录里那一份避免版本不匹配。第二条现象是在自己机器上一切正常换台机器一运行就报「Class not registered」或者十六进制的 0x80040154。原因要么是目标机没装 SolidWorks要么是装了但 ProgID 解析不到还有一种是宿主进程以 32 位启动去连 64 位的组件。解决方式是在连接之前先用 Type.GetTypeFromProgID 判空给一句人能看懂的话另外把平台目标明确设成 x64别留 AnyCPU 在路上埋雷。5.2 运行期第三条现象是跑完一批任务任务管理器里躺着一堆没有窗口的 SolidWorks 进程。原因几乎一定是 RCW 没放干净尤其是遍历组件、遍历视图的时候漏了释放。解决方式是所有 COM 对象集中走一个释放工具方法按获取的逆序释放整个批次结束时做一次完整回收绝不在循环里调。第四条现象是 SelectByID2 一直返回 false代码不抛异常。三个原因按概率排坐标传的是毫米而不是米目标实体被压缩或隐藏当前活动文档不是你以为的那个。解决方式是坐标在边界处统一换算选中前先清空选择集并确认活动文档判断失败时把坐标、文档标题、实体名一起打进日志下一次就不用猜了。第五条现象是中文环境下按名字找特征失败同一段代码在英文版上没问题。原因是代码里写死了「凸台-拉伸1」这种本地化名称。解决方式是改用特征类型判断用 GetTypeName2 拿到的类型名不随语言变配合选择类型常量做匹配或者干脆按创建顺序和草图基准面位置来定位。5.3 部署与权限第六条现象是 Add-in 注册成功注册表也写了SolidWorks 里就是看不到菜单。常见原因有三个在 64 位机器上用了 Framework 目录下的 RegAsm 注册到 32 位视图插件启动项的值被写成 0或者用户之前在插件对话框里手动取消勾选过覆盖写在当前用户下。解决方式是确认用的是 Framework64 那份注册工具检查机器级和用户级两处注册表并让用户在插件对话框里重新勾选一次。症状先看什么再看什么进程残留循环里的 COM 对象释放事件订阅是否解绑选中失败坐标单位实体是否压缩、文档是否活动属性没生效Add3 是否传了覆盖选项是否漏了重建PDF 只有一张图上次导出设置是否显式传了导出数据插件菜单不出现RegAsm 位数两处注册表还有一类不是异常的问题值得单独提RPC_E_CALL_REJECTED十六进制 0x80010001。它的意思是 SolidWorks 正在忙你的调用被拒绝。批量任务里偶尔出现是正常的正确的处理是捕获这个错误码后等两百毫秒重试重试三次还不行再放弃。很多人没见过这个码直接当成未知异常往上抛结果整批中断。6. 进阶把模板改成自己的脚手架并跑通冒烟用例模板到手只是起点真正省时间的是把它改成自己的习惯。我一般做三件事配置外置、失败可续跑、改完必跑冒烟。配置外置最直接。路径、图纸格式、属性名映射、日志级别全部挪到一个 json 里代码里只留默认值兜底。这样同一份程序A 项目用一套配置B 项目换一个文件就行不用重新编译。{ session: { visible: false, startIfMissing: true }, batch: { inputCsv: D:/work/jobs.csv, outputDir: D:/work/pdf, checkpoint: D:/work/done.txt, rebuildBeforeExport: true }, props: { drawingNo: 图号, material: 材料 } }checkpoint 那个字段是为了断点续跑。每成功一个文件就往里追加一行下次启动先读进来做差集。批量任务跑到第七百个崩掉是常事没有续跑机制就得从头再来一遍几百个文件的打开重建能耗掉一整个下午。第二件事是加冒烟用例。我习惯固定三个样本文件一个简单零件图、一个带子装配的装配图、一个多配置零件。每次改完代码先跑这三个检查四件事PDF 页数对不对、文件大小不为零、图号属性和配置表一致、任务管理器里没有残留进程。这四个检查加起来不到两分钟能挡掉绝大多数低级错误。// 冒烟检查的核心就一句失败必须显式暴露不能靠肉眼看 var result _session.RunBatch(smokeJobs, smokeOut); if (result.Failed.Count 0) throw new Exception(冒烟未通过 string.Join(;, result.Failed p a hrefhttps://download.csdn.net/download/qq_36724994/10015438 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。