资讯详情

C#获取SolidWorks选中零件名称:SelectionManager与COM互操作实战详解

📅 2026/10/19 6:20:16 | 华诺云谱 👁 阅读
C#获取SolidWorks选中零件名称:SelectionManager与COM互操作实战详解
做二次开发的人多半都经历过这种场景插件里写着写着突然就需要知道用户此刻在装配体里到底选中了哪个零件再把它的名称弹出来或者作为参数传给下一步功能。这个需求用一句话说就是——C# 获取选中零件的名称可真到动手的时候你会发现一堆琐碎的细节等着你去踩。这篇文章就是把我实际项目中反复折腾过的完整套路摊开讲一遍包括环境怎么搭、API 怎么调、为什么有些接口改名了、拿到的是“实例名”还是“文件名”以及几个最容易让人懵圈的坑。代码以常见版本为准SolidWorks 2018 到 2022 之间应该都能直接用版本差异我会在对应位置说清楚。1. 先搞清楚这个需求到底在问什么1.1 一句话需求背后的真实场景只从标题看“c# solidworks 获取选中零件的名称”好像没什么难度。但你真去拆这个需求会发现它至少能拆出三种意思第一用户在工作区里选中了一个装配体中的零部件程序要拿到这个零部件的实例名称比如“螺丝M6-1装配体1”。第二用户打开的是单个零件文件程序要拿到当前文档的文件名比如“支架.SLDPRT”。第三用户选中了一个子装配体程序不仅要知道它叫什么还得知道它在顶层装配体里的完整层级比如“子装-A-1/机架/门板”。这三种意思对应的 API 完全不同返回结果的字段也不一样。很多新手拿到的名字不对大多不是代码写错而是没搞明白自己到底要哪一种。从我的经验看最频繁出现的场景是第一种程序作为插件跑在 SolidWorks 里用户打开一个装配体然后在特征树上点选一个零件程序需要弹出这个零件的名字或者拿这个名字去读写自定义属性、做 BOM 导出。这篇文章的核心逻辑也主要围绕这个场景展开。1.2 两条路线的选择搞文档还是录宏你在网上搜这个问题搜出来的结果基本分两派。一派是照搬 API 文档把 COM 接口一个一个列出来另一派是教你直接在 SolidWorks 里录制宏看 VBA 代码怎么写的再翻译成 C#。我个人的建议是两条路都要走但是顺序很重要。先录宏再用 API 文档去确认细节。因为 SolidWorks API 的对象层级比较深直接看文档容易迷失。但是宏录制能很直观地告诉你当你选中一个零件时SolidWorks 真正调用了什么方法。录制一段、点选一个零件、停止VBA 代码里就会留下 SelectionManager 相关的调用痕迹你照着翻译错的概率会低很多。当然宏也有局限。它的 VBA 代码经常会用一堆类型不明确的变量你根本不知道它最终拿到的是对象还是字符串。这时候再去查 API 文档思路就会清晰很多。1.3 “选中零件”的歧义点还有个必须提前说明的事SolidWorks 里的“选中”有不同层级。在装配体模式下你选中特征树里的零部件返回的是 IComponent2 接口对象在零件模式下你选中一个面或一个特征返回的可能是 IFace2、IFeature在草图模式里选中一段线返回的又是 IEntity。标题里说的是“选中零件”但不同情况下返回对象的类型完全不同。所以第一步反而应该是判断当前模式再去走对应的逻辑分支。我后面给出的完整示例里会先判断当前文档是装配体还是零件再决定是通过 SelectionManager 拿选中组件还是直接读取当前文档标题。2. 核心技术拆解从 COM 到 SelectionMgr2.1 这些对象到底有什么关系SolidWorks 提供的 API 本质是一套 COM 接口C# 通过互操作Interop来调用。调用的时候你通常会先拿到一个最顶层的入口对象——SldWorks它代表整个 SolidWorks 进程。然后通过它拿到当前激活的模型文档对象 ModelDoc2 或 ModelDocEx。在文档对象下面有一个 SelectionManager选择管理器它负责告诉你当前用户选中了什么、选中了几个、每个选中对象的类型是什么。很多人搞不懂为什么要这么绕。你可能会想既然是在装配体里选零件为什么不直接从装配体里拿难点在于 SolidWorks 没有“当前选中项”这种全局静态属性它把选择信息都挂在文档对象下面。所以你必须按 SldWorks → ModelDoc → SelectionManager → 选中对象这条链路一层层往下剥。如果你是在插件环境里跑其实第一步可以更简单插件自带的连接回调函数会直接给你一个 SldWorks 对象。如果是外部独立程序就需要先启动或连接到 SolidWorks 进程。2.2 为什么是 SelectionManager 而不是 ModelDoc我见过不少新手试图用 ModelDoc2 的某个属性直接拿选中项翻半天找不到然后开始怀疑 API 是不是有问题。实际上SolidWorks 的设计就是把选择管理单独做成一个管理器。这不是为了为难你而是因为“选择”这个概念本身很复杂。一个文档里可能同时选中了多个对象可能有装配体里的零部件也有装配体自身还可能混着注解和尺寸。SelectionManager 需要统一处理这些东西所以它提供了一组带索引的方法GetSelectedObjectCount2 用来获取选中总数。GetSelectedObject6 用来取第 i 个选中对象。GetSelectedObjectType 用来判断第 i 个对象的类型。理解了这种设计你就会明白为什么代码里到处是循环和类型判断而不是简单的SelectedPart.Name。2.3 版本与接口的取舍SolidWorks 的 API 迭代很快很多方法都有多个版本后缀。比如获取选中对象的方法就有 GetSelectedObject、GetSelectedObject3、GetSelectedObject6 等。后缀数字越大通常代表能传的参数越多、支持的场景越完整。这里有一个常见的坑低版本 SolidWorks 不认识高版本的方法反之高版本大多数兼容低版本。如果你要写一个通用工具建议优先用后缀数字比较高的方法同时做好 catch遇到方法不存在时再降级。为了减少这种版本折腾我会在示例代码里大量使用 C# 的 dynamic 关键字。它可以让 COM 方法调用的写法变得很简洁不用在编译期绑定具体接口版本。缺点也很明显IDE 提示会变弱而且运行时错误要到运行那一刻才会暴露。对于这种二次开发的小工具来说利大于弊。3. 从零到一实现获取选中零件名称3.1 准备你的项目环境写代码之前先建立一个普通控制台应用程序或 WinForms 项目。我常用的配置是 .NET Framework 4.8因为 SolidWorks 的 COM 互操作在这套运行时下最省心。如果你非要用 .NET Core 或 .NET 5/6也不是不行但 COM 引用的生成和 runtime 兼容会多出不少麻烦我还真遇到过某些 API 在 .NET Core 下调用直接崩溃的情况。项目建好后给项目添加 SolidWorks 的互操作程序集。方式是右键项目引用 → 添加引用 → COM → 找到类似 “SOLIDWORKS 2018 Type Library - sldworks” 的条目勾选添加。如果你在程序集列表里找不到说明你的 SolidWorks 安装可能不完整或者系统里缺少对应组件。一般安装完整版 SolidWorks 都会带工具库。实在不行可以从安装目录里找到 sldworks.dll 对应互操作文件手动放入项目的引用目录。注意不要在项目里同时引用多个版本的 SolidWorks 互操作程序集轻则程序集冲突重则运行时直接告诉你“指定的强制转换无效”。一个解决方案只绑定一个主要版本就好。3.2 连接正在运行的 SolidWorks 实例如果我们的程序要在 SolidWorks 界面里被触发最简单的做法是先从正在运行的实例里获取对象。C# 里可以通过 Marshal.GetActiveObject 拿到这个实例。如果当前没有实例再用 ProgID 创建一个新的 COM 实例。两个分支加起来能覆盖绝大多数使用场景。using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace SolidWorksSelectionDemo { internal static class Program { [STAThread] private static void Main() { try { dynamic swApp GetSwApplication(); if (swApp null) { Console.WriteLine(无法连接 SolidWorks.); return; } Liststring partNames GetSelectedPartNames(swApp); foreach (string name in partNames) { Console.WriteLine(name); } } catch (Exception ex) { Console.WriteLine(发生异常: ex.Message); } } private static dynamic GetSwApplication() { // 方法一抓取已经打开的 SolidWorks 实例 try { return Marshal.GetActiveObject(SldWorks.Application); } catch (Exception) { // 方法二没有运行实例时创建新的实例 Type swType Type.GetTypeFromProgID(SldWorks.Application); if (swType null) return null; dynamic swApp Activator.CreateInstance(swType); swApp.Visible true; return swApp; } } } }这段代码有两点要说明。第一[STAThread]很重要COM 对象很多时候要求调用线程是 STA 模型尤其是涉及拖拽、剪贴板、窗口操作时。如果你不加程序可能在某个看似无关的位置莫名奇妙地崩溃。第二GetActiveObject拿到的对象会持有 COM 引用理想情况下用完要释放。对于短小的小工具不释放问题也不大但如果你在插件里高频调用建议掌握 Marshal.ReleaseComObject 的用法。3.3 拿到当前的激活文档拿到 SldWorks 之后下一步就是拿当前用户看到的文档。绝大部分业务逻辑都要求用户先打开一个装配体或零件然后程序才能继续。可以用 swApp.ActiveDoc 来拿。这个属性返回的是当前活动文档也就是用户在 SolidWorks 窗口最前面看到、正在编辑的那个模型文档。别小看这一步如果用户打开了三个窗口但焦点在浏览器上ActiveDoc 可能仍然会返回最后一个被激活的 SolidWorks 文档。实测下来这个属性在面对多窗口场景时还算可靠。补充一点在插件环境里如果当前没有任何文档打开ActiveDoc 会返回 null这时候你就得提示用户先开个模型再操作。示例里我会检查到 null 就直接返回。3.4 遍历所有选中对象拿到文档对象后用它的 SelectionManager 获取选择信息。完整代码如下private static Liststring GetSelectedPartNames(dynamic swApp) { Liststring result new Liststring(); // 1. 获取当前激活文档 dynamic swModel swApp.ActiveDoc; if (swModel null) { Console.WriteLine(当前没有打开的文档。); return result; } // 2. 获取选择管理器 dynamic swSelMgr swModel.SelectionManager; // 3. 统计选中对象数量 int count swSelMgr.GetSelectedObjectCount2(-1); Console.WriteLine(选中对象数量: count); // 4. 循环遍历 for (int i 1; i count; i) { int selType swSelMgr.GetSelectedObjectType(i, -1); // swSelCOMPONENTS 在 SolidWorks API 中通常为 2代表装配体里的零部件 if (selType 2) { dynamic swComp swSelMgr.GetSelectedObject6(i, -1); string compName swComp.Name2; result.Add(compName); } } return result; }这段代码已经能跑通大多数装配体场景。核心逻辑就是三步取数量、按索引取类型、从类型匹配到组件对象。这里出现的-1参数经常有人问。它表示采用默认的选择标记Selection Mark。SolidWorks 允许你用不同的标记过滤选择集大部分外部程序都用默认标记所以传-1就好。如果你的程序内部需要区分自己通过 API 添加的临时高亮和用户手动点选的内容才会用到其他标记值。3.5 区分“装配体里的零件”和“单独打开的零件”上面代码只处理了装配体里的 IComponent2。如果你的当前文档本来就是零件文件那用户选中的其实不是“零件组件”而是零件里的特征、面、草图等元素。这种情况下要显示“零件名称”直接拿当前文档的标题就好。我平时会把两种模式合并到一个方法里这样用户无论开的是装配体还是零件点击按钮后都能得到正确结果。private static Liststring GetSelectedPartNames(dynamic swApp) { Liststring result new Liststring(); dynamic swModel swApp.ActiveDoc; if (swModel null) return result; // 判断文档类型 string docType swModel.GetTypeName(); // 零件文档通常是 Part装配体文档通常是 Assembly if (docType Part) { result.Add(swModel.GetTitle()); return result; } // 装配体文档走选择管理器 dynamic swSelMgr swModel.SelectionManager; int count swSelMgr.GetSelectedObjectCount2(-1); for (int i 1; i count; i) { int selType swSelMgr.GetSelectedObjectType(i, -1); if (selType 2) { dynamic swComp swSelMgr.GetSelectedObject6(i, -1); string compName swComp.Name2; string fileName ; dynamic compDoc swComp.GetModelDoc2(); if (compDoc ! null) { fileName compDoc.GetTitle(); } result.Add(compName | fileName); } } return result; }注意GetModelDoc2()返回的并不是文件名而是一个模型文档对象。要拿到这个文档的名称需要再调用文档对象的GetTitle()。很多初学者卡在这一步会误以为组件对象的 Name 就是文件名结果拿去打开文件时发现路径不对。3.6 一个可以直接拿去改的完整示例把前面的逻辑拼到一起我这边写了一个顺手示例可以直接复制到控制台项目里改个命名空间就能用using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace SolidWorksSelectionDemo { internal static class Program { [STAThread] private static void Main() { try { dynamic swApp GetSwApplication(); if (swApp null) { Console.WriteLine(无法连接 SolidWorks.); return; } swApp.Visible true; Liststring names GetSelectedPartNames(swApp); if (names.Count 0) { Console.WriteLine(没有选中任何零件。); } else { Console.WriteLine(选中零件的名称如下:); foreach (string name in names) { Console.WriteLine(name); } } Console.WriteLine(按任意键退出...); Console.ReadKey(); } catch (COMException comEx) { Console.WriteLine(COM 调用异常: comEx.Message); } catch (Exception ex) { Console.WriteLine(普通异常: ex.Message); } } private static dynamic GetSwApplication() { try { return Marshal.GetActiveObject(SldWorks.Application); } catch (Exception) { Type swType Type.GetTypeFromProgID(SldWorks.Application); if (swType null) return null; dynamic swApp Activator.CreateInstance(swType); swApp.Visible true; return swApp; } } private static Liststring GetSelectedPartNames(dynamic swApp) { Liststring result new Liststring(); dynamic swModel swApp.ActiveDoc; if (swModel null) return result; string docType swModel.GetTypeName(); if (docType Part) { result.Add(swModel.GetTitle()); return result; } dynamic swSelMgr swModel.SelectionManager; int count swSelMgr.GetSelectedObjectCount2(-1); for (int i 1; i count; i) { int selType swSelMgr.GetSelectedObjectType(i, -1); if (selType 2) { dynamic swComp swSelMgr.GetSelectedObject6(i, -1); string compName swComp.Name2; string fileName ; dynamic compDoc swComp.GetModelDoc2(); if (compDoc ! null) { fileName compDoc.GetTitle(); } result.Add(compName | fileName); } } return result; } } }运行这个程序前建议先在 SolidWorks 里打开一个有装配体的项目然后在装配体中选中几个零件再运行程序。你会看到控制台输出两列信息左边是组件实例名右边是文件名。3.7 拿到名称之后能做什么名称往往只是入口。实际开发里拿到 IComponent2 对象之后通常还要继续往下挖。比如要读取零件的自定义属性可以用swComp.GetModelDoc2()拿到文档对象再调用GetCustomInfo或Extension.CustomPropertyManager。要获取零件的物理质量可以用swComp.GetMassProperties()。要控制零件隐藏或显示可以用swComp.Visible属性。只要你保住了 IComponent2 这个对象引用后续扩展非常方便。所以写代码时不要把方法限定成“只返回字符串”很多时候直接返回对象让上层调用方按需取值会更灵活。4. 实操之后才懂的细节与避坑清单4.1 实例名、文档名和文件名的分叉这是整个需求里最容易踩的坑。组件对象 IComponent2 的Name2属性返回的是“实例名”不是“文件名”。简单理解实例名是这个零件在当前装配体里那一次放置的编号。比如你往装配体里放了同一个“螺丝6.SLDPRT”三次那么这三处分别叫“螺丝6-1装配体1”“螺丝6-2装配体1”“螺丝6-3装配体1”。如果你在同一个装配体里再次放置同一个零件序号会接着往上走。而文件名是磁盘上的实际文件名比如“螺丝6.SLDPRT”。如果你要显示给用户看的“零件名”大多数场景用户期望的是文件名而不是实例名。因为实例名会变重装装配体后序号重新排列文件名才是稳定的标识。所以我代码里特意把两个都取出来用|分隔供你取舍。如果你只需要唯一的零件标识我建议优先取文件名。如果你需要区分同一文件在装配体里被多次放置的不同实例那就必须取实例名。4.2 在子装配体里选中的零件怎么拿完整路径这个问题有业务场景时会很头疼。假设顶层装配体里有一个子装配体“子装-A”用户点选了“子装-A”里面的一个“盖板”。此时你拿到的组件对象位于子装配体内comp.Name2返回的是类似“盖板-1子装-A”这样的字符串而不是顶层装配体里的完整路径“子装-A/盖板-1顶层”。如果你需要构造整机的 BOM 树这种“局部名称”显然不够用。此时可以玩一个技巧利用GetPathName或者递归向上寻找父层级。但我更常用的方式是借助 SolidWorks 的Component2.GetChildren递归遍历整棵组件树然后比对组件对象引用是否相等。这个方法稳定、直观就是代码量多一点。核心代码如下private static void WalkTree(dynamic parentComponent, dynamic targetComp, string path, Liststring results) { if (parentComponent null) return; if (ReferenceEquals(parentComponent, targetComp)) { results.Add(path); return; } dynamic children parentComponent.GetChildren(); if (children null) return; foreach (dynamic child in children) { string childPath path / child.Name2; WalkTree(child, targetComp, childPath, results); } }注意ReferenceEquals对 COM 对象不一定可靠因为 RCWRuntime Callable Wrapper可能不是同一个包装对象。比较稳妥的方式是通过GetModelDoc2()拿到的文档对象指针或者用GetType()配合对象特有属性做对比。如果你只是简单应用也可以比较Name2字符串但要承担重名实例带来的风险。4.3 界面线程和 COM 调用的稳定性问题外部程序调用 SolidWorks API偶尔会出现程序假死或者 API 调用超时。一个常见原因是线程模型不对。前面提到[STAThread]这里再展开一点。SolidWorks 的 COM 对象很多是 STA单线程套间对象如果你从后台线程直接调用COM 会帮你做线程切换但如果主线程正在等待这个后台线程结束就会互相等待造成死锁。我见过最典型的死锁场景是界面上放了一个“开始导出”按钮点击后开了一个后台线程去遍历装配体遍历过程中又弹出进度条窗口进度条窗口在主线程上显示。这时候后台线程在等主线程处理消息而主线程在等后台线程完成程序就卡死了。解决办法有三种。第一除非耗时不长否则尽量在 UI 线程直接调用 API别开多线程。第二必须多线程时在后台线程里调用 API 前后不要阻塞主线程等待把结果用异步回调带回。第三如果一定需要等待那就用Dispatcher.Invoke或Control.Invoke把 UI 更新丢回 UI 线程。这种问题不会在前几十次调用中出现但等到你处理大型装配体操作次数突然变多线程切换频繁问题就会集中爆发。提前在设计阶段考虑清楚能省不少事。4.4 常见错误速查表我整理了一下平时二次开发里最常碰到的错误直接做成速查表遇到问题先对照一遍。错误信息可能原因对应解决办法0x80040154 Class not registered本机没装 SolidWorks或互操作组件未正确注册检查 SolidWorks 安装完整性修复安装0x80004005 一般性错误调用了当前版本不支持的 API或对象已释放确认 API 版本避免释放后继续使用对象0x80020009 异常被捕获但参数不正确dynamic 调用时参数类型不对检查 GetSelectedObjectType / GetSelectedObject6 的参数类型ActiveDoc 为 nullSolidWorks 已启动但没有打开文档提示用户先打开模型文件Name 属性获取失败对象类型不是 IComponent2 而是 IFeature先判断 SelectionManager 返回的类型再取值程序启动就崩溃控制台项目没加 [STAThread] 或缺少引用补上 STAThread重新添加互操作引用这张表不能覆盖所有情况但能覆盖我遇到过的百分之七八十。尤其第一行很多刚接触 COM 的同事一看到“class not registered”就慌其实多半就是本机 SolidWorks 没装好或者没启动。4.5 一个被低估的调试方法录制宏反向翻译再分享一个很多老鸟都在用但新人不太知道的技巧宏录制。需求复杂到一定程度时靠文档猜测 API 调用顺序是不现实的。我的流程是这样的先在 SolidWorks 里打开“工具 → 宏 → 录制”手动执行你要实现的操作比如在装配体里选中一个零件、切换一下文档视图、读取某个属性。操作完成后停止录制SolidWorks 会生成一段 VBA 代码。这段 VBA 代码虽然语法老但它把 API 调用的顺序、参数个数、对象层级都写得很清楚。比如你想知道“如何拿到当前选中的零件名称”录制完选择操作后代码里会看到SelectionManager、GetSelectedObject、GetTitle这些关键字。然后你在 C# 里照着逻辑写一遍即可。这个方法不仅能帮你确认 API还能帮你快速了解某个按钮在后台到底做了什么。很多时候所谓“接口不会调”其实是没找到正确的入口录制一段就全明白了。4.6 批量处理与性能注意事项如果你要遍历一个大型装配体里的所有选中零件然后逐个读取属性那性能就要重视起来。第一个原则是能复用对象就不要重复获取。比如GetModelDoc2()第一次拿到文档对象后同一个组件多次调用理论上返回同一个文档但每次调用仍有 COM 开销。最好是拿到一次缓存起来后面循环使用。第二个原则是注意内存释放。遍历几百上千个组件后COM 包装对象不会自动全部销毁。如果你发现处理多次后内存暴涨、程序变慢多半是 RCW 对象堆积。建议在处理完单批数据后调用Marshal.ReleaseComObject释放对象。不过这里有个更接地气的提醒不要在一开始就为性能优化写一堆复杂代码。先保证功能正确在中等规模装配体上测试如果速度能接受就不要再折腾。过早优化会引入生命周期管理的 bug到时候追起来很痛苦。5. 关于这个功能的一些个人体会技术点讲完了最后说点我在实际项目里的体会。“获取选中零件的名称”作为需求是简单的但它牵扯出来的问题非常多你到底要哪个名称、当前是什么文档类型、对象是组件还是特征、要不要考虑多选、要不要处理子装配体的层级关系。这些问题如果不提前想清楚往往写出来的代码只能在特定场景下运行用户换个操作方式就崩。我现在的习惯是写这类小工具时先把需求边界画清楚然后用宏录一遍用户操作再决定用哪一组 API。宁可开头多花半小时梳理也不要代码跑两天才发现拿错名字。另外如果你的程序要发布给别人用尽量把“名称”的定义写进说明里。比如界面上显示的是“文件名|实例名”还是只显示“文件名”。这个细节很多人注意不到但实际使用时特别影响体验。我在交付过一个工具后被用户连续追问了几次“为什么名字对不上”最后发现是双方对这个名词的理解不一样。最后再分享一个小技巧如果你在 C# 里用 dynamic 调试很痛苦可以把 COM 对象序列化成关键字符串打印出来比如先打出类型名、可用属性名初步定位问题后再回到正式代码。这种“ Dump 式调试”在 COM 互操作这种黑盒环境里比断点单步调试要高效得多。希望这篇分享能帮你少走一些弯路。如果看完后你的项目里已经把零件名称顺利拿到手了说明这套思路对你是有效的。后续如果还要做批量导出 BOM、按名称批量改属性也欢迎在评论区继续聊。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑