这个系列标题我已经挂在草稿箱里有一阵子了。在论坛和社群里答复过不少这种问题:“我用C#能不能控制SolidWorks自动建模?”“为什么我按教程写的代码连不上SolidWorks?”“VB我会一点,C#到底怎么下手?”说实话,SolidWorks API的资料不算少,但要么是官方文档那种信息密度极低的白皮书风格,要么就是上来直接贴一个几百行的插件工程,新手根本看不进去。所以我想写一套真正面向新手的引导,第一篇先把最基础的环境、连接、运行流程讲清楚,让你能在半小时内用C#写出一个能读取当前模型特征的独立程序。读完这篇文章,你会知道API二次开发到底是什么形态、为什么首选C#、开发环境怎么配、怎么连接SolidWorks,以及怎么通过宏录制加速自己的API学习速度。
1. 二次开发整体思路:先确定你要做“独立程序”还是“插件”
1.1 SolidWorks API到底是什么
SolidWorks本身是图形软件,窗口上的每一次点击、每一项参数修改,背后其实都对应着一条或一组命令。API做的事情,就是把用户手动操作变成代码调用,让你在程序里指挥SolidWorks干同样的活。比如“打开一个零件”“遍历特征树”“修改自定义属性”“批量另存为其他格式”,这些都能通过API完成。
这套接口在底层是COM组件模型,官方提供的类型库文件在SolidWorks安装目录下,主要就几个SolidWorks.Interop.sldworks.dll、SolidWorks.Interop.swconst.dll,前者是接口定义,后者是各种枚举常量的定义。C#通过COM互操作直接引用这些程序集,就能像调用普通类库一样调用SolidWorks的功能。这一点决定了后续所有开发方式的走向。
1.2 独立程序和插件怎么选
围绕SolidWorks API,业界有两种典型的开发形态:
- 独立程序(.exe):自己起一个进程,通过COM接口连接正在运行的SolidWorks实例,或者创建新的SolidWorks进程。程序与SolidWorks各自独立,调试方便,适合做批量工具、辅助工具、数据处理脚本。
- 插件(Add-in,.dll):以DLL形式注册到SolidWorks进程内,在SolidWorks菜单栏或工具栏挂上自己的功能按钮,随SolidWorks启动而加载,适合做需要深度集成、交互频繁的正式产品。
新手我强烈建议从独立程序开始。原因很简单:独立程序就是一个控制台或WinForm程序,主流程清晰,断点调试不会拖垮SolidWorks本体,出错时最坏就是重启一下SolidWorks。插件开发涉及COM注册、SwAddin接口实现、命令管理器、图标资源等一系列繁琐机制,很多东西要自己搭骨架,对新手来说负担太重。这个系列的前几篇都围绕独立程序展开,等后面专门安排一篇讲插件工程的搭建。
对于“C#为什么能外挂SolidWorks”这个疑问,本质就是C#借助COM互通协议,拿到SolidWorks暴露出的对象模型,然后在进程外调用这些COM接口。C#在托管语言里和COM互操作已经算非常成熟的了,语法清晰,有强类型提示,开发效率明显高于VB。
2. 环境准备与引用配置
2.1 开发环境的基本选型
先说一下我建议的开发组合,这套组合经过大量项目验证,踩坑最少:
- SolidWorks:你用的正常版本即可。但要注意,SolidWorks版本和API程序集是有对应关系的,装哪个版本就用哪个版本的程序集,后面会细说。
- Visual Studio 2019或2022,社区版就够用。
- 项目类型:控制台应用即可,用来跑第一个测试样例;后面做界面的话再切WinForms或WPF。
- 目标框架选用.NET Framework 4.7.2或4.8,不建议用.NET 6/8做SolidWorks互操作的入门学习。
为什么框架上我特别强调.NET Framework?因为SolidWorks的官方COM接口和大量历史文档、示例都是基于.NET Framework时期的技术栈。.NET Core/5+虽然也能通过ComWrappers之类的机制做COM调用,但坑非常多,网上能找到的现成代码往往也不直接兼容,新手很容易卡在莫名其妙的环境问题上。做SolidWorks二次开发的绝大多数老手,项目文件里还是.csproj的老格式配.NET Framework。先把这个路径走通,再考虑新框架,战略上最稳妥。
2.2 添加引用与Copy Local设置
创建好控制台项目后,右键“添加引用”。可以直接通过“浏览”按钮,去SolidWorks安装目录找DLL。一般路径是:
C:\Program Files\SolidWorks Corp\SolidWorks\在这个目录下能看到SolidWorks.Interop.sldworks.dll和SolidWorks.Interop.swconst.dll。部分地区或定制版安装路径会不一样,你可以直接在文件管理器里搜索SolidWorks.Interop*。
添加完这两个引用之后,有两件事必须立刻做:
- 选中引用,把“嵌入互操作类型”(Embed Interop Types)设置为
False。如果不改,项目编译时会把COM类型信息嵌入程序集,运行阶段经常出现“无法将类型为X的对象强制转换为类型为Y”的诡异错误,这是新手最容易遇到的坑之一。 - 设置“复制本地”(Copy Local)为
False。因为SolidWorks的程序集是按版本强签名的,运行时由本机SolidWorks目录提供即可,没必要也不应该复制到输出目录,否则换一个SolidWorks版本就会因为版本不一致报错。
另外,如果你以后要开发插件,还会用到SolidWorks.Interop.swpublished.dll,这个只有插件工程才会引用。命令控制还需要SolidWorks.Interop.swcommands.dll。现在入门阶段,先用两个就够了。
下面这个表方便你对照:
| 程序集文件 | 命名空间 | 主要作用 |
|---|---|---|
| SolidWorks.Interop.sldworks.dll | SolidWorks.Interop.sldworks | 核心对象接口,如SldWorks、IModelDoc2、FeatureManager |
| SolidWorks.Interop.swconst.dll | SolidWorks.Interop.swconst | 枚举常量,如swDocPART、swDocumentTypes_e |
| SolidWorks.Interop.swpublished.dll | SolidWorks.Interop.swpublished | 插件开发注册相关接口 |
| SolidWorks.Interop.swcommands.dll | SolidWorks.Interop.swcommands | 插件命令ID定义 |
2.3 C#代码里如何引入命名空间
引用配置好之后,在代码文件顶部加上两行using,这是每个SolidWorks C#项目的标配:
using SolidWorks.Interop.sldworks; using SolidWorks.Interop.swconst;前者给你SldWorks、IModelDoc2这些核心接口类型,后者给你swDocPART这类枚举值。后面写代码时如果智能提示找不到类型,先回头检查这两行有没有漏。
3. 连接SolidWorks实例
3.1 连接正在运行的SolidWorks
做独立程序,第一步就是“拿到”SolidWorks的Application对象。这个对象是所有API调用的根,你可以把它理解为整个SolidWorks程序的控制台。
如果用户已经打开了SolidWorks,最直接的连接方式是用Marshal.GetActiveObject:
using System.Runtime.InteropServices; SldWorks swApp = null; try { swApp = (SldWorks)Marshal.GetActiveObject("SldWorks.Application"); } catch (COMException) { // 没有找到正在运行的实例,走创建逻辑 }这段代码的原理是,SolidWorks在系统ROT(Running Object Table)里注册了自己的实例标识SldWorks.Application,GetActiveObject会把那个对象拿回来,再强转成SldWorks接口。说白了就是“去房间里喊一声王工在不在,在就直接用他的工位”。
3.2 创建新的SolidWorks进程
如果当前没有运行中的SolidWorks,或者用户想直接由程序启动一个新进程,则用ProgID创建:
Type swType = Type.GetTypeFromProgID("SldWorks.Application"); SldWorks swApp = (SldWorks)Activator.CreateInstance(swType); swApp.Visible = true;GetTypeFromProgID会查找注册表中SolidWorks注册的COM类信息,Activator.CreateInstance负责把实例创建出来。创建完成后设置Visible = true,否则新进程可能是隐藏的,用户会一脸懵。
这里有一个非常重要的细节:项目编译目标平台必须和SolidWorks位数一致。新版SolidWorks都是64位程序,如果你的C#项目在Visual Studio里被设置成了x86,那么Activator.CreateInstance创建COM实例大概率会失败或者行为异常。解决办法很简单:在“项目属性 -> 生成 -> 平台目标”里选择x64,或者至少选“AnyCPU”时勾选“首选32位”不要勾选。这个问题在SolidWorks 2018以后的版本特别明显,我见过太多人卡在这里。
3.3 封装一个可复用的连接方法
每次写程序都复制粘贴获取连接的代码很烦。我的习惯是把它封装成一个静态方法,放到一个公共类里:
using System; using System.Runtime.InteropServices; using SolidWorks.Interop.sldworks; public static class SwAppHelper { public static SldWorks Connect() { SldWorks swApp = null; try { swApp = (SldWorks)Marshal.GetActiveObject("SldWorks.Application"); } catch (COMException) { swApp = null; } if (swApp == null) { Type swType = Type.GetTypeFromProgID("SldWorks.Application"); if (swType == null) throw new Exception("本机未安装SolidWorks,或COM注册信息缺失。"); swApp = (SldWorks)Activator.CreateInstance(swType); swApp.Visible = true; } return swApp; } }以后每个独立程序里直接调SwAppHelper.Connect(),简单省事。为什么推荐先GetActiveObject再CreateInstance?因为用户很多时候已经开着SolidWorks和模型了,直接连接现有的实例能无缝操作当前文档,省去自己重新打开模型文件的流程。只有连不上时才启动新实例,这样的逻辑对用户最友好。
3.4 关于COM对象释放的提示
新手常常会在网上看到“用完后要Marshal.ReleaseComObject释放”的说法,于是写了一大堆繁琐的释放代码。我个人的建议是:独立程序里,在程序退出前不必特意释放COM引用,进程结束时系统会自动清理。如果在循环里频繁创建COM对象,那确实要注意释放,否则SolidWorks可能越跑越慢。但与其纠结释放,不如先确保“获取”和“调用”的正确性,释放的问题后面专门讲COM生命周期时再展开。尤其不要对同一个COM对象多次调用ReleaseComObject,那会直接把SolidWorks内部引用计数打乱,轻则抛异常,重则整个SolidWorks崩溃。
4. 第一个可运行示例:遍历当前模型的特征树
4.1 完整代码
这一节我给你一个能直接跑的最小工程,作用是把当前模型的特征树遍历一遍并打印到控制台。这个例子麻雀虽小,涉及的ActiveDoc、FeatureManager、Feature这三个对象,是后续几乎所有开发都要用的核心对象模型。
using System; using System.Runtime.InteropServices; using SolidWorks.Interop.sldworks; public static class SwAppHelper { public static SldWorks Connect() { SldWorks swApp = null; try { swApp = (SldWorks)Marshal.GetActiveObject("SldWorks.Application"); } catch (COMException) { swApp = null; } if (swApp == null) { Type swType = Type.GetTypeFromProgID("SldWorks.Application"); if (swType == null) throw new Exception("本机未安装SolidWorks,或COM注册信息缺失。"); swApp = (SldWorks)Activator.CreateInstance(swType); swApp.Visible = true; } return swApp; } } class Program { static void Main(string[] args) { SldWorks swApp = SwAppHelper.Connect(); IModelDoc2 doc = swApp.ActiveDoc; if (doc == null) { Console.WriteLine("当前没有打开任何模型。"); return; } Console.WriteLine("当前文档:" + doc.GetTitle()); FeatureManager featMgr = doc.FeatureManager; Feature feat = featMgr.FirstFeature(); while (feat != null) { PrintFeature(feat, 0); feat = feat.GetNextFeature(); } Console.WriteLine("遍历完成,按任意键退出。"); Console.ReadKey(); } static void PrintFeature(Feature feat, int depth) { string name = string.IsNullOrEmpty(feat.Name) ? "(无名特征)" : feat.Name; Console.WriteLine(new string(' ', depth * 2) + name); Feature sub = feat.GetFirstSubFeature(); while (sub != null) { PrintFeature(sub, depth + 1); sub = sub.GetNextSubFeature(); } } }4.2 逐段解释关键逻辑
这段代码看起来不长,但每一部分背后都有值得讲清楚的点。
swApp.ActiveDoc返回的是当前SolidWorks中处于激活状态的模型文档。注意它可能为null,比如SolidWorks开了但没有任何文档,或者当前激活的是浏览器窗口。所以代码里要做空判断,这是每次获取文档后的标准安全检查。
doc.FeatureManager是特征树管理器,它提供FirstFeature()方法返回特征树里的第一个特征。然后通过GetNextFeature()沿着兄弟节点往下遍历,直到返回null。GetFirstSubFeature()和GetNextSubFeature()则是向下钻取子特征用的,比如一个拉伸特征下面的草图、圆角等。
理解特征树结构对SolidWorks二次开发至关重要。你完全可以把Feature想象成一个树节点,每个节点可能有子节点,遍历方式和二叉树遍历几乎一样,只是节点数量和层级不固定。这段递归打印的代码,其实就是你后面做“批量修改特征名”“识别特定类型特征并改参数”的基础。
4.3 运行结果长什么样
假设用户在SolidWorks里打开一个简单的轴类零件,特征树是:
轴-驱动端.sldprt ├── 凸台-拉伸1 │ └── 草图1 ├── 切除-拉伸1 │ └── 草图2 └── 圆角1运行程序后,控制台会输出:
当前文档:轴-驱动端.sldprt 凸台-拉伸1 草图1 切除-拉伸1 草图2 圆角1 遍历完成,按任意键退出。注意这里的Feature.Name在某些情况下可能为空字符串,比如一些虚特征、参考特征,所以代码里做了空值兜底。真正常见的坑是:用户想看特征的“类型”,比如区分拉伸和切除,光靠Name不靠谱,因为用户可能给特征改成奇怪的名字。要判断特征类型,应该用Feature.GetTypeName2()返回的系统类型名,例如“Extrusion”“Cut”等,这个后面讲到特征操作时再细说。
4.4 从被动录到主动写的关键一步
第一次跑通遍历特征树之后,我建议你试试手动建几个特征,比如改名、加材质、加注解,然后再运行程序看看特征树输出有什么变化。这样能帮你快速建立起“模型结构”和“API对象模型”之间的映射关系。
很多时候新手去看SolidWorks官方API帮助,几百个接口绝对会迷路。我的方法是:先记住一条主线,SldWorks->IModelDoc2->FeatureManager->Feature,先把这棵对象树爬熟了,其他接口都是围绕这条主干开枝散叶的。
5. 新手提升效率的速学方法:宏录制配合C#
5.1 为什么推荐宏录制
如果你以前没接触过SolidWorks API,直接让你记住每个API方法是很痛苦的。我自己刚入门时也有这个阶段,记不住接口名、记不住参数顺序,搜索引擎翻半天结果还是一知半解。后来我发现最快的学习方式其实就在软件里:宏录制。
SolidWorks自带宏录制功能,在菜单“工具 -> 宏 -> 录制”开启,然后你对模型做的操作会被自动翻译成VBA代码。录制完成后保存,再在“工具 -> 宏 -> 编辑”里打开就能看到。这段VBA代码就是你刚才操作的API调用序列。虽然语言是VBA,调用的却是完全相同的对象模型和方法名,所以把它转成C#并不难。
举个最简单的例子,我录一个动作:打开一个有活动文档的SolidWorks,然后什么也不做,只点一下“属性”看文档名。录出来的VBA大致是:
Dim swApp As Object Dim swDoc As Object Set swApp = Application.SldWorks Set swDoc = swApp.ActiveDoc Debug.Print swDoc.GetTitle转成C#就是:
SldWorks swApp = SwAppHelper.Connect(); IModelDoc2 swDoc = swApp.ActiveDoc; Console.WriteLine(swDoc.GetTitle());本质上Application.SldWorks对应的就是我们前面封装的Connect()方法,其他对象方法和属性几乎一一对应。
5.2 VBA转C#时的三个典型差异
第一个差异是对象声明方式。VBA里大量使用As Object或者干脆不声明,类型在运行时才知道。C#里必须写明确类型,所以你要根据变量实际调用的方法去猜接口类型。这个一开始比较痛苦,但反过来也是一个学习过程:需要SketchManager就声明成ISketchManager,需要Feature就声明成Feature。
第二个差异是默认属性和可选参数。VBA允许省略一些默认属性,比如Debug.Print swDoc.GetTitle里的GetTitle()括号都能省略。C#不行,必须是完整的方法调用,而且很多COM方法有可选参数,C#里往往要通过重载或传递Type.Missing来补齐。
第三个差异是枚举常量。VBA宏录制代码里会出现swDocPART这类的常量,这是SolidWorks定义在swconst里的枚举。在C#里要用(int)swDocumentTypes_e.swDocPART或者直接转换成对应枚举,注意类型匹配。
5.3 录宏学习API的正确姿势
录宏不是让你照着抄,而是看它“调用了哪些对象、哪些方法”。你需要做的是:
- 把一次操作录制成VBA。
- 通读代码,标出调用了哪些对象(如
FeatureManager、SketchManager、SelectionManager)。 - 查API帮助或在Visual Studio里输入“对象名.”看智能提示,了解这些接口还有哪些别的方法。
- 自己动手用C#重新实现一遍同样的功能,不要直接翻译粘贴。
录宏最大的价值在于,它是SolidWorks官方给出的“标准操作对应的API道路图”。比如你不知道怎么创建拉伸特征,就手动录一个拉伸,VBA代码会告诉你顺序是先建草图还是先选面,调用的方法名是什么,参数怎么传。这套路学会之后,你的学习速度会快很多。
不过录宏也不是万能的。录制出来的代码往往包含大量冗余调用,比如界面刷新、视图操作之类,你要学会抓主干、去枝叶。另外宏录制适合学习“做一件事的流程”,但不太适合学习“如何设计一个健壮的程序”,业务逻辑、异常处理、用户交互还是要靠你自己设计。
6. 常见问题与排查技巧实录
6.1 编译报错“命名空间不存在”
这个错误最常见的原因是引用没加对,或者加了但命名空间写错。检查一下项目里是否有SolidWorks.Interop.sldworks和SolidWorks.Interop.swconst这两个引用,然后确认代码顶部有对应的using语句。如果引用添加正确但智能提示里还是看不到SldWorks,大概率是引用了错误的DLL——注意不要引用SolidWorks.Interop.swpublished.dll来做独立程序,那个是插件开发用的,里面的接口命名空间和普通API不同。
6.2 运行时提示“无法将类型为…的对象强制转换为类型为…”
这种问题绝大多数和Embed Interop Types有关。请在引用属性里把“嵌入互操作类型”设为False再重新编译。如果这个问题是在项目迁移或别人电脑上运行时才出现,还要检查目标机器的SolidWorks版本是否与开发时一致,或者是否安装了多个版本的SolidWorks导致GAC里注册信息混乱。
6.3 ActiveDoc一直返回null
有几种常见原因。第一种是SolidWorks里确实没打开模型,这好办,手动打开一个零件或装配体再试。第二种是SolidWorks窗口虽然开着,但当前激活的是SolidWorks的某些内部窗口,比如资源管理器面板、属性页面,ActiveDoc拿不到文档。这种情况你需要确保用户真的激活了一个模型窗口,或者在代码里更稳妥地遍历所有打开的文档。遍历文档可以用:
int docCount = swApp.DocumentCount(); object[] docNames = (object[])swApp.GetDocumentNames();先拿到文档数量,再根据文档名做处理,比单纯依赖ActiveDoc更可靠。
6.4 后台线程调用API崩溃
SolidWorks的COM对象模型对线程非常敏感,COM对象的调用必须始终在同一个Apartment线程。新手最典型的错误是,开了一个Task或Thread,然后在后台线程里直接调用swApp.ActiveDoc,结果程序莫名其妙崩溃或卡死。
我的建议是:独立程序里,所有SolidWorks API调用都在主线程完成。如果你的程序需要做耗时的批量处理,可以先把需要的数据读取到普通C#对象里,处理在后台线程做,处理完再回到主线程写回SolidWorks。如果以后要用WinForms/WPF做界面,还需要考虑到UI线程和COM线程的关系,这一部分等讲到界面开发时再详细展开。
6.5 64位环境下进程位数不匹配导致的报错
前面已经强调过一次,再重复一遍:新版SolidWorks是64位,C#项目“平台目标”必须设置为x64,否则Activator.CreateInstance或者COM调用会莫名失败。判断方法也很简单,在Visual Studio“项目属性 -> 生成 -> 平台目标”里确认一下是x64,不是x86。如果你是用AnyCPU并勾选了“首选32位”,那也等于x86,一样会踩坑。
下面这个速查表是这套入门配置下最常遇到的问题:
| 现象 | 最常见原因 | 解决方向 |
|---|---|---|
| 编译找不到SolidWorks类型 | 没添加引用或using写错 | 浏览SolidWorks安装目录添加sldworks和swconst引用 |
| 运行时报类型转换异常 | Embed Interop Types = True | 互操作嵌入设为False后重新编译 |
| ActiveDoc为null | 模型未打开或激活窗口不正确 | 确保有激活文档,或用DocumentCount遍历 |
| CreateInstance失败 | 平台位数不一致 | 平台目标改为x64 |
| 后台线程调用崩溃 | 跨线程访问COM对象 | API调用收敛到主线程执行 |
| 换个电脑运行报版本错误 | SolidWorks版本与程序集不匹配 | 重新添加目标机器的程序集并重新编译 |
6.6 一个容易被忽略的细节:程序启动时SolidWorks已经退出了怎么办
如果你的程序先启动,用户中途又手动关掉了SolidWorks,那么所有后续API调用都会抛COMException。独立程序要做好这个异常捕获,并在捕获后提示用户重新打开SolidWorks或者让程序重新创建一个实例。我的建议是把整个业务循环包在一个try-catch里,遇到COM错误时提示并退出,而不是让程序默默崩溃。
还有一个相关的细节:如果你用CreateInstance创建了新的SolidWorks进程,用户关闭SolidWorks主窗口只是隐藏程序,进程可能还在后台。代码里看到“SolidWorks主窗口关了但进程没退”是常见现象,这不是你的程序问题,而是COM对象的生命周期和SolidWorks主窗口的显示状态并不完全绑定。对于新手,先知道有这回事,等以后做健壮程序时再针对性处理。
结尾
这个系列写第一篇时,我特意把内容控制在“能跑起来”这个最小范围里。在我看来,新手学SolidWorks API最大的障碍不是某个API不会用,而是第一公里太长——环境没配好、引用没设对、连接不上实例,任何一个环节卡住都会让人打退堂鼓。你在跟着文章跑通这个遍历特征树的程序后,后续学建模、装配、工程图、属性批量处理,都是在同一套对象模型上做文章。
我个人在实际操作中的体会是,连接和遍历特征树这套代码,你最好自己新建项目敲一遍,不要复制粘贴。敲的过程里你会注意到哪些方法返回的是object、哪些地方要强转、哪些调用返回null需要判断。这些感觉是看代码永远体会不到的。等到你敲熟了,再回头看SolidWorks帮助文档里那些接口列表,会发现它们不再是一堆枯燥的名字,而是一棵你心里已经有数的树。下一篇我打算讲如何用C#批量操作自定义属性,那时候遍历特征树的思路就能直接派上用场了。