ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

C#直连SolidWorks API:从零实现模型自动化处理与批量属性读取

C#直连SolidWorks API:从零实现模型自动化处理与批量属性读取

这段时间在写一个自动处理三维模型的工具,绕不开 SolidWorks 的 API。网上资料零零散散,要么是宏代码,要么是VB写的老古董,C# 直连的完整示例很少。自己踩了一堆坑之后,把最实用的“直连”路线整理出来,包含可以直接复制的代码和几个必须注意的坑。这篇文章适合两类人:一类是想脱离手工打开 SolidWorks 挨个导图纸、改属性的工程师;另一类是刚接触 SolidWorks 二次开发,想用 C# 写第一个能跑的连接程序。我会尽量把“为什么这么做”也讲清楚,这样你改起来不慌。

1. 直连 SolidWorks 的整体思路:为什么选 C# 和 COM

1.1 直连解决的痛点:从文件交换到 API 自动化

很多人处理 SolidWorks 和外部系统的数据交换,第一反应是导出中间格式,比如 STEP、IGES,或者读自定义的文本文件。这种方法最简单,但缺点很明显:丢特征、丢装配关系,而且没法从模型里反向写入数据。举个例子,你要批量读取一百个零件的质量属性,或者改掉几十个文件的“材料”属性,靠文件导出基本做不到。

直连的意思,就是让 C# 程序通过 COM 接口直接驱动 SolidWorks 应用。你可以把 SolidWorks 想象成一台带遥控器的机器,COM 接口就是遥控器上那些按钮。按下“打开文件”的按钮,模型就打开了;按下“读取属性”的按钮,数据就返回回来了。机器仍然是 SolidWorks,只是操作者从人变成了程序。

这种方式的优势在于:

  • 不产生中间文件,直接在内存中操作,速度快。
  • 能覆盖 SolidWorks 的绝大部分原生功能,不只停留在读写文件。
  • 可以进行批量、循环、带判断逻辑的复杂操作。
  • 能把你手工操作的标准流程固化下来,避免重复劳动和误操作。

劣势同样存在:必须本机装有 SolidWorks 且授权可用,版本兼容性需要留意,COM 对象是进程外调用,性能有一定消耗。但对大多数内部自动化工具来说,这些都可以接受。

1.2 早期绑定与后期绑定的取舍

C# 调用 COM 组件有两种常见姿势:加引用和用 dynamic。

加引用,也就是在 Visual Studio 里右键“添加引用”,选“COM 库”,找到 SolidWorks 的 Interop 类型库。这种方式叫早期绑定,编译器直接生成强类型接口,代码里能看到 IntelliSense,属性方法不会写错,运行时的性能也好一些。缺点是:你的开发机必须安装对应版本的 SolidWorks,且配置好类型库,否则引用加不上。

用 dynamic 调用,通过 Activator.CreateInstance 或类型库加载器传入 ProgID,创建一个运行时对象,再用 dynamic 去调用方法。这种方式叫后期绑定,代码上省掉了强类型引用的麻烦,但代价是没有智能提示,写错了只能运行时才发现,而且性能稍差。

我的建议是:做“简单实现”时,优先用早期绑定。原因很直接,开发效率高,不容易因为拼写错误卡半天。但有个前提,你编译出来的程序,目标机器上的 SolidWorks 主版本要和你引用类型库的版本一致或兼容。否则运行时会提示“方法不存在”之类的异常。关于版本问题,我们后面单独说。

1.3 最小方案的总体流程

所谓“简单实现”,我把它拆成四个步骤:

  1. 连接:获取或创建一个 SolidWorks 应用对象。
  2. 打开:用 OpenDoc6 打开指定模型文件。
  3. 操作:读取或修改模型数据,比如拿文件名、质量、自定义属性。
  4. 清理:保存文档、关闭文档、释放 COM 引用。

下面所有的代码,都以这四个步骤为主线。你完全可以把这段代码当模板,跑通之后再往里面加自己的业务逻辑。

2. 环境准备:跑通直连前要做的三件事

2.1 确认 SolidWorks 版本与 .NET Framework

这一步容易忽略,但直接影响结果。SolidWorks 官方 API 通常每个主版本对应一组互操作类型库,比如 SolidWorks 2020 和 SolidWorks 2021 的接口版本不一定完全一样。你自己开发时,最好保证开发机的 SolidWorks 版本和最终运行环境一致。如果没法一致,起码主版本要相同,比如都是 2020。

然后是 .NET 版本。SolidWorks 的 COM 接口是老牌 COM,理论上所有 .NET 版本都能调。但如果你用的是 .NET 6/8 这类新项目,添加 COM 引用时会发现“互操作程序集”的生成和旧的 .NET Framework 项目不太一样,有些类型库可能识别不全。我在实际项目里最稳的组合是:.NET Framework 4.8 + x64 控制台应用。SolidWorks 本身是 64 位进程,你的程序最好也编译成 x64,否则在某种环境上可能出现位元不匹配的异常。

如果你非要用 .NET Core 或 .NET 5+,也不是不行,需要手动引用互操作 DLL,或者用 dynamic 方式绕过引用。但这会增加复杂度,不适合“简单实现”的目标。我建议先老老实实用 .NET Framework 4.8。

2.2 在项目中添加 SolidWorks COM 引用

打开 Visual Studio,创建一个控制台应用(.NET Framework),然后在解决方案资源管理器里右键项目,选择“添加引用”,左侧选“COM”,在列表里找到类似“SolidWorks 2020 Type Library”的项。不同版本显示的名称略有差异:

SolidWorks 版本类型库名称示例
2018SolidWorks 2018 Type Library
2020SolidWorks 2020 Type Library
2022SolidWorks 2022 Type Library

勾选后确认,Visual Studio 会在项目的 bin 目录下生成 Interop.SldWorks.dll 之类的文件。

这里有个坑:列表里可能会出现多个相似的条目,比如“SolidWorks 2020 Type Library”和“SolidWorks 2020 Constant Type Library”。我们要选的是前者。后者只是常量定义,单独引用它没有用。如果两个都勾选了,也没关系,主要是前者的接口。

添加完引用后,代码里 using SldWorks; 就能使用 SldWorks 命名空间下的类型了。

2.3 设置启动权限与运行方式

SolidWorks 是个桌面应用,直接连它和普通后台服务不一样。如果你的程序想在没有管理员权限的情况下运行,基本没问题。但如果你跑的是自动化脚本,且需要操作 SolidWorks 正在使用中的文件,建议以管理员身份运行一次程序,确认权限。也可能遇到的情况是,SolidWorks 以管理员权限运行了,而你的程序不是管理员权限,那么 COM 调用会失败。反过来也一样。解决办法很简单:要么都普通权限,要么都管理员权限。

另外,如果你是在服务器上想用 SolidWorks API,注意 SolidWorks 的授权通常不允许无界面服务器调用,而且 COM 的窗口消息机制在非交互会话下不稳定。我一般只在有桌面的环境里做自动化,不做无人值守服务。

3. 核心代码:从连接到打开模型的最小实现

3.1 连接已运行的 SolidWorks 实例

先看最简单的场景:你的电脑上已经手动打开了一个 SolidWorks 窗口,程序要去“依附”它。在 COM 里,我们透过“运行对象表”来拿到正在运行的实例。C# 里可以直接用 Marshal.GetActiveObject。

using System; using System.Runtime.InteropServices; using SldWorks; namespace SolidWorksConnector { class Program { static void Main(string[] args) { SldWorks.ISldWorks swApp = null; try { swApp = (SldWorks.ISldWorks)Marshal.GetActiveObject("SldWorks.Application"); Console.WriteLine($"连接成功,版本:{swApp.RevisionNumber()}"); } catch (Exception ex) { Console.WriteLine($"连接失败:{ex.Message}"); } } } }

这段代码里,"SldWorks.Application"是 SolidWorks 的 ProgID。只要 SolidWorks 正在运行,GetActiveObject 就能拿到它的顶层对象。注意这里用到的类型是 ISldWorks,不是 SldWorks.ApplicationClass。这也是很多人容易踩坑的地方:从类型库里看,应用对象有两个“长得很像”的类型,一个是接口,一个是 CoClass。接收返回值时,用接口接最安全。

如果你的程序連不上现有实例,通常是因为 SolidWorks 根本没开,或者权限不匹配。后面我们细说。

3.2 创建新的 SolidWorks 应用实例

更多自动化场景里,我们不想让 SolidWorks 一直开着,而是让程序自己拉起一个新进程。这就要用 Type.GetTypeFromProgID 加 Activator.CreateInstance。

Type swType = Type.GetTypeFromProgID("SldWorks.Application"); SldWorks.ISldWorks swApp = (SldWorks.ISldWorks)Activator.CreateInstance(swType);

创建出来的对象默认是不可见的,因为 SolidWorks 启动时以后台模式运行,会先把主窗口隐藏起来。如果你需要看到界面,可以调用swApp.Visible = true。注意 ISldWorks 接口里有个Visible属性,布尔型,直接赋值即可。

提示:用后台模式跑模型处理时,建议保持swApp.Visible = false,处理速度更快,也不会弹一堆窗口干扰桌面。需要调试时再临时改成 true。

不管是 GetActiveObject 还是 CreateInstance,拿到的 swApp 都是同一个顶层入口。后续操作完全一致。

3.3 打开零件/装配体并读取基础信息

打开文件我们使用 OpenDoc6。这个方法是 SolidWorks API 里打开文档最标准的方式,参数非常多,前面五个是关键,后面的可以给默认值。

public const int swDocPART = 1; public const int swDocASSEMBLY = 2; public const int swDocDRAWING = 3; string filePath = @"D:\Models\TestPart.SLDPRT"; int errors = 0; int warnings = 0; ModelDoc2 modelDoc = swApp.OpenDoc6( filePath, swDocPART, (int)swOpenDocOptions_e.swOpenDocOptions_Silent, "", ref errors, ref warnings); if (modelDoc != null) { Console.WriteLine("打开成功:" + modelDoc.GetTitle()); }

参数解释:

  • 第一个:文件完整路径。
  • 第二个:文档类型。1 零件,2 装配体,3 工程图。
  • 第三个:打开选项。swOpenDocOptions_Silent表示安静打开,不显示保存提示等弹窗。这个值在不同版本里都是一个整数常量,可以直接写 0 也能打开。
  • 第四个:配置名称。默认留空字符串即可。
  • 第五、第六个:错误和警告,需要ref传出,调用前先定义变量。

打开成功后,返回的是一个ModelDoc2对象。这个对象包含了模型文档的几乎所有操作入口,比如Extension、GetFeatures()、GetCustomPropertyValues等。

拿到模型对象后,第一件想做的事通常是读质量属性。用Extension.GetMassProperties可以拿到很多工程数据。

object[] props = modelDoc.Extension.GetMassProperties( (int)swMassPropertyType_e.swMassPropertyType_Regular, null, false, null, null, null); // props 数组里包含体积、面积、质量等信息 Console.WriteLine($"质量:{props[2]} kg");

GetMassProperties 的返回值比较特殊,是一个 object 数组,下标从 0 开始依次是:体积、表面积、质量等。这个数组对应关系在不同版本里基本稳定,但建议只读取你需要的项,并且加一个异常保护。

3.4 遍历特征树与属性提取

特征树是 SolidWorks 模型的结构核心。通过modelDoc.FeatureByPositionReverse(0)可以按顺序取到每个特征。遍历的代码很简短,但要注意顺序:默认是从特征树最末位开始向前取,如果你想从顶部开始,可以用FeatureByPositionReverse(treeLen - 1)之类的循环。我实际操作时,更常用的是先拿特征树的数量,再从头遍历。

int featCount = modelDoc.GetFeatureCount(); for (int i = featCount - 1; i >= 0; i--) { Feature feat = modelDoc.FeatureByPositionReverse(i); Console.WriteLine($"特征:{feat.Name},类型:{feat.GetTypeName()}"); }

这里有个细节:FeatureByPositionReverse(i)当 i 从 0 开始到 featCount-1,依次返回的是特征树末端的特征。如果你想从头开始,就倒着传 i。

自定义属性的读取,在工程图标题栏、BOM 表格场景里太常用了。模型的自定义属性分为“配置特定”和“摘要”两类。读取配置特定属性的方法如下:

string val = modelDoc.CustomPropertyManager("").Get("材料").Value;

其中CustomPropertyManager("")传空字符串代表默认配置。如果你的模型有多个配置,可以把配置名字传进去。如果属性不存在,Value 可能是空字符串或特殊值,需要自己处理。

3.5 保存与关闭的正确姿势

处理完模型后,保存和关闭别随便调。如果直接modelDoc.Close(),程序不会提示,但 SolidWorks 内部会判断是否保存过,直接关闭会丢失改动。正确做法是先主动保存或另存为。

// 保存组件 modelDoc.Save(); // 另存为 STEP 文件,如果你有这个需求 modelDoc.SaveAs(@"D:\Output\TestPart.STEP");

SaveAs有个重载会把它当成另一种格式对待。但注意,SaveAs 的第二个参数如果传 0,表示保存当前格式;如果你要另存为 STEP,需要传对应的格式值。通常我更推荐用swDoc.SaveAs3方法,它的参数更明确,可以指定swSaveAsOptions。

关闭文档用swApp.CloseDoc(modelDoc.GetTitle()),或者modelDoc.Close()。我一般用前一种,因为 CloseDoc 传入文档名称即可,避免引用的对象状态不清。

最后,无论有没有异常,都要在程序结束前释放 COM 资源。这是一个看起来简单但很多新手会漏掉的点。

Marshal.FinalReleaseComObject(modelDoc); Marshal.FinalReleaseComObject(swApp); GC.Collect(); GC.WaitForPendingFinalizers();

不过要注意:如果你激活的是已经手动打开的 SolidWorks 实例,千万别调用swApp.ExitApp()。否则你直接把用户的 SolidWorks 给关了。只有自己创建的新实例,才应该调用ExitApp()。

4. 进阶技巧与实用场景扩展

4.1 批量处理文件夹中的模型

直连的价值不在单文件,而在批处理。假设你要把一个文件夹下所有 SLDPRT 文件的质量读取出来,逻辑就是把上面的打开代码包到一个循环里。

string[] files = System.IO.Directory.GetFiles(@"D:\Models", "*.SLDPRT"); DataTable dt = new DataTable(); dt.Columns.Add("文件", typeof(string)); dt.Columns.Add("质量kg", typeof(string)); foreach (string file in files) { int error = 0, warn = 0; ModelDoc2 doc = swApp.OpenDoc6(file, swDocPART, (int)swOpenDocOptions_e.swOpenDocOptions_Silent, "", ref error, ref warn); if (doc != null) { object[] props = doc.Extension.GetMassProperties( (int)swMassPropertyType_e.swMassPropertyType_Regular, null, false, null, null, null); dt.Rows.Add(file, props[2]); swApp.CloseDoc(doc.GetTitle()); } }

批量操作时有三个细节:

  • 处理完一个文件要立刻 CloseDoc,不然内存占用会越来越高。
  • 如果模型比较大,可以在打开前用swOpenDocOptions_ReadOnly选项,防止误改原文件。
  • 遇到打不开的文件不要终止循环,记录一行错误继续下一个。

你会发现,这个批量处理的循环结构,几乎可以套用在所有 SolidWorks 自动化需求上:改属性、导出工程图、更新质量,都一样。

4.2 与 WinForm 进度条集成

控制台程序适合做后台逻辑,但给同事用或者自己看进度时,带个界面友好得多。WinForm 里,SolidWorks 的 COM 调用是同步的,会阻塞 UI 线程。如果你直接在按钮事件里跑一个大批量循环,界面就会卡死。

解决办法有两个。简单粗暴的:把循环体放到Task.Run里,但在 Task 里访问 UI 线程控件需要小心,可以用Invoke。更推荐的做法:用 BackgroundWorker 或 System.Windows.Forms.Timer 做进度刷新。

我的做法是,在Task.Run里跑任务,用IProgress<int>上报进度,UI 层订阅这个事件。这样既能解耦,又不容易出跨线程问题。SolidWorks 的 COM 调用本身是线程安全的吗?严格说不是。COM 对象的调用最好在单线程模型里完成,所以 Task.Run 里的循环内不要再并发调用,保持一个线程处理整串操作就没问题。

4.3 从文档属性中读取配置用于 BOM

BOM(物料清单)是制造业里最常用的东西。很多公司会把零件号、材料、重量放在自定义属性里。直连之后,你可以直接读取这些属性,不用再手工一个个看。

刚才提到的CustomPropertyManager已经能读默认配置。如果你的模型存在多个配置,每个配置的属性可能不一样,需要遍历配置。

string[] configNames = (string[])modelDoc.GetConfigurationNames(); foreach (string config in configNames) { CustomPropertyManager cpm = modelDoc.CustomPropertyManager(config); string partNo = cpm.Get("零件号").Value; Console.WriteLine($"配置:{config},零件号:{partNo}"); }

注意GetConfigurationNames()返回的是数组,但某些旧版本 API 返回object[],用之前先转一下。这个功能在自动出 BOM 时非常有用,配合 Excel 导出,就能做一个自动化的 BOM 生成工具。

5. 常见问题与排查速查表

5.1 类型库加载失败与版本不匹配

现象:添加引用时找不到 SolidWorks 类型库,或者编译时提示“未能嵌入互操作类型”。

原因与对应处理:

  • 电脑没装 SolidWorks,或安装的版本太旧。必须装完整版,精简版可能不带类型库。
  • 类型库名称被版本数字分隔,列表里搜不到,可以按“SolidWorks”关键词模糊搜索。
  • Visual Studio 的“嵌入互操作类型”属性被设置成了 True。在老项目中经常会因为嵌入互操作类型导致 COM 接口转换报错。解决办法:选中引用,在属性面板把“嵌入互操作类型”改成 False。

5.2 连接不到正在运行的 SolidWorks

现象:Marshal.GetActiveObject抛出“操作无法使用”或“找不到 ActiveObject”。

常见原因:

  • SolidWorks 未启动,必须先启动一个实例。
  • 你的程序和 SolidWorks 权限不一致,一个管理员一个普通。统一起来。
  • 跨进程调用时,目标类型 ProgID 拼写错误。检查字符串是SldWorks.Application,不是SolidWorks.Application。

5.3 对象释放不干净导致进程卡死

现象:程序执行完后,SolidWorks 进程仍在后台,或内存居高不下。

原因:COM 对象没有彻底释放。SolidWorks API 对象树很复杂,子对象(ModelDoc、Feature、Extension)都需要释放。最简单的兜底方案:

Marshal.FinalReleaseComObject(doc); Marshal.FinalReleaseComObject(app); GC.Collect();

但如果你反复操作了很多次,某个对象没被引用到,还是可能出现进程残留。我建议写一个清空所有封装对象的 Dispose 方法,把每层对象都置为 null,然后用 GC 强制回收两次。另外,程序退出时正常调用swApp.ExitApp()也能解决大部分问题——前提是这实例是你自己创建的。

5.4 运行权限与后台模式注意事项

很多后台自动化脚本用 Windows 计划任务触发。计划任务在系统启动时运行,运行上下文可能是非交互会话,SolidWorks 的 COM 调用会失败。务必设置成“只在用户登录时运行”,并且勾选“使用最高权限运行”。否则你可能会看到程序启动后毫无反应,进程列表里却有一个僵死的 SolidWorks。

另一个奇怪的问题:SolidWorks 窗口不可见时,某些对话框服务会等待。比如打开文件时缺省模板、缺少参考引用,会弹模态对话框。如果 SolidWorks 不可见,对话框会一直挂在后台,程序看起来像卡死了。应对方式是:打开时使用swOpenDocOptions_Silent,必要时再配合swOpenDocOptions_AutoRecovery之类的选项,尽量抑制弹窗。

5.5 常见异常与解决对照

异常信息通常原因处理方式
拒绝访问权限不足或 COM 未初始化使用管理员权限,或在 Main 开头调用SetProcessDPIAware不是必要的,重点检查权限
方法“OpenDoc6”没有重载引用了错误版本的 Interop检查 Early Bound 类型库版本,或用 dynamic
对象引用未设置为实例没有创建 swApp 或连接失败用调试断点检查 swApp 是否为 null
内存耗尽批量循环没有释放对象每个文件操作完立即 CloseDoc + FinalReleaseComObject

这些大多不是算法问题,纯粹是 COM 开发的“日常”。遇到别慌,按表格先排查,大部分能很快解决。

6. 一些心得体会

6.1 用事件加回调提升稳定性

SolidWorks API 提供了事件接口,比如文档打开、保存、选择变化等。如果你的自动化工具需要监控用户的动作,可以用事件绑定。但事件会引入进程间消息循环,处理不当容易让程序变卡。我的经验是:简单实现阶段先不要碰事件,用主动轮询的方式去查状态已经够了。等核心流程稳定后,再按需绑定特定事件。

6.2 调试技巧:让 SolidWorks 窗口可见

后台模式虽然快,但调试时什么也看不到,出了问题也不好分析。建议开发阶段在swApp.Visible = true下运行,并且把 SolidWorks 的界面布置成你熟悉的布局。这样每一步操作的效果都能直接看到,能明显减少排查时间。等代码稳定要交付时,再切回不可见模式。

6.3 后续扩展方向

这套直连基础打好之后,可以扩展的方向不少:

  • 自动出工程图:调用DocExport或SaveAs输出 PDF/DWG。
  • 自动更新“设计者”“日期”等属性,用于版本管理。
  • 从 SolidWorks 里读取装配体约束关系,同步到自研的 BOM 系统。
  • 配合Async模式做模型校核,批量做干涉检查。

我个人认为,C# 直连 SolidWorks 最大的价值是“把重复手工操作变成可重复的脚本”。不要一上来就想做大而全的框架,先搞定最简单的“连接—打开—读取—关闭”这条链路,就能应付大部分日常工作。最后说一个我在实际项目中踩过的小坑:打开文件后如果程序异常退出,SolidWorks 可能一直报“该文件已在另一处打开”,这时手动关掉残留进程即可,不用重启电脑。后来我写了个 try-catch 和 finally,把关闭文档和释放对象都放在了 finally 里,这个问题就基本绝迹了。希望这份最小的实现能帮你少走点弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进