
插件写好了功能调通了双击dll注册完打开SolidWorks准备验收结果“工具 插件”列表里死活看不到你的插件名或者列表里有了一勾选SolidWorks直接给你表演一个闪退。这两件事是我这些年做SolidWorks插件开发遇到最多的场景尤其是用C#写SwAddin插件的时候几乎十个崩溃里有九个都发生在注册与加载阶段而不是业务逻辑本身。这次做第二版插件我又把整个注册链路从头到尾查了一遍顺手把注册表、Regasm、注册回调这些关键点整理出来。如果你正在用C#开发SolidWorks插件或者插件已经写好但卡在“让SolidWorks认识它”这一步这篇就是给你准备的。1. 插件注册到底在注册什么先搞懂SolidWorks的加载链条1.1 SolidWorks眼中的插件就是一套COM组件SolidWorks本身是原生C应用它不认识C#不认.NET更不在乎你用的是什么语言写插件。它唯一认的是COM。C#写的插件在编译之后通过COM Interop层把自己包装成一个进程内COM组件。SolidWorks要做的只有一件事拿着一个GUID调用CoCreateInstance把这个组件创建出来然后看这个对象有没有实现ISwAddin接口。如果有就调用ConnectToSW把SolidWorks主对象ISldWorks塞给你。从这一刻起你的插件才真正拿到了SolidWorks的控制权可以挂菜单、接事件、遍历模型树。理解这个链条特别重要因为后面所有奇怪现象都能用它解释插件列表里看不到名字 → SolidWorks在注册表里没找到你的“身份标识”。能看到名字但勾选没反应 → COM类创建失败或者ConnectToSW根本没被调用或者调用后返回了false。一勾选就闪退 → COM对象创建成功了但创建过程中某个环节炸了最典型的包括ConnectToSW里的空引用、事件回调被垃圾回收、旧dll路径残留下加载了不匹配的版本。所以排查插件注册问题本质上就是在排查这条链路上的四个环节注册表里有没有GUID记录 → COM能否创建对象 →ConnectToSW有没有正确执行 → 插件代码是否在SolidWorks环境里稳定运行。每一步都有对应的验证方法。1.2 注册表里与插件直接相关的两道门把上面的过程拆开C#插件的注册其实包含两层信息。第一层是Windows COM注册。这一层让系统知道“这个GUID对应哪个dll、哪个类”这样CoCreateInstance才能找得到目标。C#组件一般用Regasm完成这一层注册或者通过Visual Studio的“Register for COM Interop”编译选项在生成时自动做。第二层是SolidWorks自己的插件名单。这一层让SolidWorks知道“这个GUID对应的组件是一个可以显示在插件列表里的SwAddin”。这一层不在COM注册里而是写在SolidWorks自己的注册表键下。最核心的两个键是HKEY_CURRENT_USER\Software\SolidWorks\Addins\{GUID} HKEY_CURRENT_USER\Software\SolidWorks\AddinsStartup\{GUID}Addins键负责让插件出现在“工具 插件”对话框中AddinsStartup键负责控制“启动时加载”的勾选状态。两者作用完全不同但很多教程喜欢混在一起说导致很多人漏掉其中一个。除了HKCUHKEY_LOCAL_MACHINE\Software\SolidWorks\Addins\{GUID}也存在同样结构。做个人开发和内部工具分发强烈建议只写HKCU。原因是HKLM写入需要管理员权限而且有些公司电脑组策略限得比较死注册表重定向可能把人坑死。HKCU对普通用户可写SolidWorks以自己的身份运行也能读得到对单机开发来说最简单、最稳。1.3 为什么很多教程让你把注册代码写在类里很多人第一接触插件注册第一反应是手动用regedit建键。这在开发机上临时实验没问题但插件做出来是要分发的你不可能让同事去手动建注册表。所以通常做法是把注册逻辑写进一个静态方法上面打[ComRegisterFunction]标记。这个特性是.NET COM Interop提供的一个回调机制当组件被Regasm注册时系统会自动调用这个静态方法把你想做的额外注册动作一并完成。注销时对应触发[ComUnregisterFunction]。换句话说Regasm负责完成“COM注册”这层你的ComRegisterFunction负责完成“SolidWorks插件名单”这层。两层拼起来才是C#插件注册的完整闭环。下面第三部分我会把这套写法完整拆开这里先不着急因为在那之前还有个更基础的问题要解决你的工程本身得先符合COM和SolidWorks的预期。2. C#插件工程的地基属性和注册回调怎么配2.1 动手写代码前先把三个工程属性改对C#插件的注册能不能成功从工程创建那一刻就决定了。我见过太多人代码写得没问题结果卡在项目属性上浪费一整天。第一目标平台务必设为x64。从SolidWorks 2021开始绝大部分发行版本都是64位进程。如果你的程序集目标是AnyCPU或x86Regasm会把COM注册写进32位注册表视图SolidWorks以64位进程去查COM信息时根本查不到。这就是“注册了半天系统里明明有记录SolidWorks就是不认识”的最常见原因。在Visual Studio里选中项目进入“生成”页把“平台目标”明确改成x64不要用AnyCPU。第二打开“Register for COM Interop”。这个选项在“生成”页底部勾上之后编译时会自动调用Regasm完成基本的COM注册。注意开启它需要以管理员身份运行Visual Studio否则编译会报权限相关的错误。很多人在这一步卡住其实是忘了用管理员模式打开VS。第三确保程序集是COM可见的。一种是直接在AssemblyInfo.cs里加[assembly: System.Runtime.InteropServices.ComVisible(true)]另一种更精准的做法是在插件类上用[ComVisible(true)]标记。我习惯两者都做程序集级别全部暴露类级别再加[Guid]和[ComVisible(true)]一目了然。这三个设置是后续所有注册动作的前提任何一个不对后面注册回调写得再漂亮也没用。2.2 一个能跑起来的最简插件类下面这个类是一个最小可用的SwAddin骨架。它实现了ISwAddin接口并且在ConnectToSW里留下一行日志输出方便确认到底有没有被加载。using System; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using SolidWorks.Interop.sldworks; namespace MySwAddin { [Guid(3FA1B9C0-5A2D-4E6B-9D2C-8B6A1E7F4A23)] [ComVisible(true)] public class SwAddin : ISwAddin { private ISldWorks _app; private int _cookie; public bool ConnectToSW(object ThisSW, int Cookie) { _app (ISldWorks)ThisSW; _cookie Cookie; // 第一件事写日志。后面排查全靠这一行 File.AppendAllText( C:\Temp\myaddin.log, DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss) ConnectToSW called, dll Assembly.GetExecutingAssembly().Location Environment.NewLine); // 在这里挂菜单、接事件 return true; } public bool DisconnectFromSW() { // 在这里卸载菜单、释放资源 return true; } } }注意ISwAddin接口的版本不同个别版本还会要求实现StartUpNotification之类的方法。如果编译时提示接口未完全实现按提示补一个空实现就行不影响注册逻辑。SolidWorks的互操作程序集可以在安装目录下找到SolidWorks.Interop.sldworks.dll和SolidWorks.Interop.swconst.dll直接添加引用即可。ConnectToSW返回true表示加载成功返回false会被SolidWorks判定为插件加载失败且通常不会有任何提示。所以在方法体里写日志是最低成本的自我保护。2.3 注册动作必须绑定到COM注册回调类有了接下来解决“注册时把SolidWorks名单一并写好”的问题。在同一个类里加两个静态方法[ComRegisterFunction] public static void RegisterFunction(Type t) { // 注册逻辑 } [ComUnregisterFunction] public static void UnregisterFunction(Type t) { // 注销逻辑 }ComRegisterFunction会在Regasm注册该程序集、注册到该类型所在的COM类时被调用。这个设计的好处是所有注册动作集中在一个地方不需要额外写安装脚本开发机上编译一次就等于完成注册装到别人机器上也只要做一次COM注册动作SolidWorks名单自动跟着写入。但这里有一个非常容易踩的坑这两个方法必须是静态的参数必须是Type而且Type是“当前被注册的类的运行时类型”。如果方法签名写成了无参数或者把参数类型写成了别的Regasm会报错或干脆不调用。另外这两个方法不能有第二个参数所有额外信息都从Type t上拿。3. 注册函数全拆解GUID、注册表键和值到底怎么填3.1 类型GUID必须拿准程序集GUID是两回事注册函数拿到的Type t就是插件类本身所以最稳的做法是用t.GUID来生成注册表路径。string keyPath Software\SolidWorks\Addins\ t.GUID.ToString(B);ToString(B)输出的是带花括号的GUID例如{3FA1B9C0-5A2D-4E6B-9D2C-8B6A1E7F4A23}这正是注册表里常见的格式。有人图省事从AssemblyInfo.cs里拿[assembly: Guid(xxx)]然后把字符串写死结果类上改GUID时忘了同步注册表路径和组件实际GUID不一致SolidWorks勾选时创建不到对象表现就是“有名字但加载没反应”。这种问题最难查因为没有报错。所以统一一条规则注册表路径里的GUID永远从t.GUID取不让字符串写死。插件类上的[Guid]是唯一数据源。3.2 Addins键和AddinsStartup键分别怎么填完整注册逻辑如下我一行一行解释。[ComRegisterFunction] public static void RegisterFunction(Type t) { string addinKey Software\SolidWorks\Addins\ t.GUID.ToString(B); using (Microsoft.Win32.RegistryKey key Microsoft.Win32.Registry.CurrentUser.CreateSubKey(addinKey)) { key.SetValue(null, MySwAddin 显示名称); key.SetValue(Description, 第二版C#插件); } string startupKey Software\SolidWorks\AddinsStartup\ t.GUID.ToString(B); using (Microsoft.Win32.RegistryKey key Microsoft.Win32.Registry.CurrentUser.CreateSubKey(startupKey)) { key.SetValue(null, 1); } }Addins路径下默认值也就是SetValue(null, ...)写入的那个空名称值存的是插件在SolidWorks插件对话框中显示的名字。这个值用什么内容并不严格一般用你自己的插件名即可。Description是辅助描述SolidWorks界面不一定显示但写上没坏处。AddinsStartup路径下默认值写1表示SolidWorks启动时自动加载这个插件写0或删掉这个键则不自动加载用户可以在插件列表里手动勾选。这里有个细节要特别说明不同SolidWorks版本对这个默认值的类型有差异有的按REG_SZ字符串读有的按REG_DWORD读。最稳的做法是先用Regasm注册好一个官网示例插件或者注册一个同事机器上能正常工作的插件然后打开regedit看AddinsStartup下那个GUID的默认值类型是什么照着抄。我自己踩过2018版和2024版行为不一致的坑所以现在写注册逻辑前都会先看一眼目标版本的习惯。补充一个判断技巧如果插件注册完列表里能看到名字但“启动时加载”怎么勾选都保持不了或者重启SolidWorks后自动加载失效十有八九就是AddinsStartup下默认值的类型或数据写错了。3.3 注销函数要做得干净彻底反注册函数比注册函数更考验仔细因为清理不干净会留下“幽灵插件”——SolidWorks列表里还显示但勾选加载永远失败每次打开SolidWorks还会弹错误提示。[ComUnregisterFunction] public static void UnregisterFunction(Type t) { string addinKey Software\SolidWorks\Addins\ t.GUID.ToString(B); Microsoft.Win32.Registry.CurrentUser.DeleteSubKeyTree(addinKey, false); string startupKey Software\SolidWorks\AddinsStartup\ t.GUID.ToString(B); Microsoft.Win32.Registry.CurrentUser.DeleteSubKeyTree(startupKey, false); }DeleteSubKeyTree的第二个参数false表示如果键不存在不抛异常直接忽略。这个参数一定要带上否则你反注册一个本来就没写完的插件时会得到一个离谱的RegistryKeyNotFoundException被打个措手不及。另外注意ComUnregisterFunction只在Regasm执行/unregister时触发。如果你只是手动删了个dll没跑反注册那SolidWorks名单里的键会一直留着。这也是后面第五部分要讲的“旧注册残留”问题的根源。3.4 顺手把插件自己的配置也写进注册表SolidWorks的插件开发中除了上面这些“名册”键你经常还需要保存自己的配置比如版本号、许可证状态、用户设置。这个可以写在同一个Addins路径下也可以单独建一个键。我一般单独建一个string configKey Software\MyCompany\MySwAddin; using (Microsoft.Win32.RegistryKey key Microsoft.Win32.Registry.CurrentUser.CreateSubKey(configKey)) { key.SetValue(Version, 2.0.0); }用HKCU的好处是每个Windows用户有自己的配置多用户环境不会互相干扰而且普通权限就能写不需要管理员。要注意别往HKLM下写配置一是权限问题二是程序升级卸载后HKLM残留很容易被权限卡住删不掉。4. 首次注册实测Regasm、SolidWorks验证和崩溃排查4.1 Regasm命令、codebase和32/64位的恩怨工程编译完成后插件列表写好了COM注册这层还得靠Regasm来落地。如果开发时勾选了“Register for COM Interop”每次编译已经自动注册过但发布到别的机器或手动分发dll时通常需要手动敲命令。64位SolidWorks环境用的是64位RegasmC:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe MySwAddin.dll /codebase如果你是在32位环境下临时测试或者SolidWorks确实还是32位老版本再用Framework目录下的32位RegasmC:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe MySwAddin.dll /codebase/codebase这个参数非常关键。不加它Regasm只会在注册表里写CLSID信息但不会告诉COM组件“这个dll放在哪个路径”一旦程序集不在GAC里CoCreateInstance就找不到文件表现为插件列表能看到名字一勾选就报错或毫无反应。加上/codebase后Regasm会写一条CodeBase注册表项指向dll的绝对路径组件进程外也能被定位。/codebase有一个副作用就是注册表中会存着dll的绝对路径。dll换目录后必须重新注册否则SolidWorks会沿着旧路径找dll然后报文件不存在。这个坑在开发阶段尤其常见因为你会频繁改输出目录。每次移动dll之后都重新跑一次Regasm。注册完可以验证COM信息是否写对。用注册表编辑器看这个路径是否存在HKEY_CLASSES_ROOT\CLSID\{你的GUID}\InprocServer32正常情况下里面会有一条CodeBase值内容是完整dll路径。注意在64位系统上在regedit里看到的HKEY_CLASSES_ROOT\CLSID是HKLM\Software\Classes\CLSID和HKCU\Software\Classes\CLSID合并视图路径没写对时这里也能快速暴露问题。4.2 打开SolidWorks验证插件加载的完整流程注册完打开SolidWorks按下面几条逐一验证在“工具 插件”对话框中确认列表里出现了你的插件名称。如果没有说明Addins键没有生效优先检查注册表路径和GUID。勾选插件观察是否立即加载。能加载的话ConnectToSW里的日志文件应该出现新的一行而且日志里记录的dll路径必须是你刚注册的那个路径。加载后检查你挂的菜单、工具栏或命令是否出现。如果菜单没出现但日志显示已调用ConnectToSW那问题就转到UI构建代码那边和注册无关了。如果设置了自动加载重启SolidWorks后插件应当无需手动勾选就自动生效而且不会弹出错误对话框。我现在做验证时第一步永远是看日志文件。哪天觉得“改了代码但SolidWorks还是旧行为”也是先看日志确认当前加载的dll到底是哪个路径。这个习惯帮我挡掉了至少五六次“假BUG”。4.3 插件不显示和加载崩溃的完整排查链路以我碰到过的真实情况C#插件注册阶段的失败可以归纳成一张排查表。顺着这张表排查比看SolidWorks日志有效率得多现象可能原因检查动作列表中完全没有插件Addins键没写或GUID不对或Regasm位数不对核对注册表路径、GUID确认用64位Regasm列表有但勾选无反应COM注册失败缺少CodeBase或程序集不在目标位置查看CLSID下是否有CodeBase重新Regasm /codebase勾选后SolidWorks闪退ConnectToSW内部异常或事件委托被GC回收或旧dll残留看日志是否进入ConnectToSW用ExceptionHandler包住初始化代码启动SolidWorks就崩AddinsStartup键指向了一个无法加载的旧插件删除AddinsStartup下的GUID键逐个二分定位重启后自动加载失效AddinsStartup默认值类型或数据不对插件加载被系统取消对比正常插件的键值类型重新写入GC回收导致崩溃这一点值得单独提醒一下。C#插件里很常见的一个写法是这样SwApp.OnIdle OnIdle; // 在ConnectToSW里挂事件这个写法初看没问题但如果OnIdle后面没有用一个类级别的字段引用C#编译器不会帮你保活这个委托实例垃圾回收一触发委托就被回收了。等SolidWorks那边触发了事件CLR尝试调用一个已经被回收的委托直接崩溃或静默失败。这不是注册的问题但和注册有很大关系——它通常在你第一次勾选插件、SolidWorks加载插件后的一两分钟内爆发很容易被误判成“注册导致崩溃”。解决办法是用一个字段保存委托实例比如private void OnIdle() { }然后挂载的时候写成_onIdleDelegate OnIdle; SwApp.OnIdle _onIdleDelegate;ConnectToSW里的初始化代码尽量用try-catch包住任何异常都要写进日志。原因很简单SolidWorks是原生进程C#插件抛出的托管异常通常不会弹给用户而是被COM Interop层吞掉表现成“闪退”或“勾选无反应”。把异常落地到日志文件你才看得到真相。5. 第二版插件的教训旧注册残留、清理脚本与日志定位5.1 “改了没生效”多半是旧注册残留这次做第二版我最开始被一个问题折磨了很久代码改完重新编译、重新注册打开SolidWorks后跑的居然还是旧逻辑。查了半天最后发现是旧版dll的注册信息还残留在注册表里。COM组件的注册信息是按GUID索引的。如果你的新版插件沿用了旧版一样的[Guid]但dll路径变了或者输出目录变了CoCreateInstance会先按注册表里的CodeBase找到旧路径发现文件没了再去搜其他位置结果加载到另一个目录下同名dll代码当然还是旧版本。更隐蔽的场景是旧版GUID和新版GUID不同但旧的Addins键还留在SolidWorks注册表里。这样插件列表里会出现两个同名或相似名字的插件你勾选新版时SolidWorks偶尔还会把旧版一起加载两个插件的菜单、事件互相打架行为完全失控。这里有一个原则凡是插件GUID变化或者dll输出路径变化一定要先把旧版从SolidWorks名单里彻底清掉再做新注册。5.2 写一个清理脚本把反注册变成傻瓜式手工去regedit删键太容易漏。我给自己写了一个批处理脚本每次切版本前先跑一遍内容如下echo off set GUID{3FA1B9C0-5A2D-4E6B-9D2C-8B6A1E7F4A23} reg delete HKCU\Software\SolidWorks\Addins\%GUID% /f reg delete HKCU\Software\SolidWorks\AddinsStartup\%GUID% /f reg delete HKCU\Software\SolidWorks\AddinsStartup%GUID% /f 2nul echo 清理解除注册... C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe MySwAddin.dll /unregister如果用的是安装包分发而不是裸dll就把这些删除逻辑放进安装工程的卸载脚本里。卸载时务必调用Regasm/unregister让ComUnregisterFunction把SolidWorks的两个键一并删干净。很多人卸载插件后SolidWorks还残留错误提示基本都是漏了这一步。清理完注册表再做一次完整注册打开SolidWorks验证。这个流程在第二版开发中我觉得已经养成了习惯任何一次怀疑“加载的不是最新代码”第一步就是全量清理第二步重新注册第三步看日志。三步走完过滤掉了八成环境问题。5.3 用日志锁定当前dll的真实路径日志里记录程序集路径这个习惯在插件切换多目录时尤其救命。要注意如果直接调用Assembly.GetExecutingAssembly().Location在部分环境下它可能返回空字符串。更稳的是用Assembly.GetExecutingAssembly().CodeBase然后去掉file:///前缀。不过实测下来只要dll不是从网络共享位置加载Location基本可用。我在第二版插件里把日志行扩展成这样File.AppendAllText( C:\Temp\myaddin.log, DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss) dll Assembly.GetExecutingAssembly().Location codebase Assembly.GetExecutingAssembly().CodeBase version Assembly.GetExecutingAssembly().GetName().Version Environment.NewLine);这样每次加载日志里都能看到哪个路径下的dll、哪个版本号被SolidWorks真正加载了。配合注册表里CLSID下的CodeBase值一起看基本能判断是不是旧注册残留如果日志里的路径和dll实际文件路径不一致或者版本号和预期不一致马上就锁定是注册表结构问题而不是业务代码问题。另外在ConnectToSW里给_app赋值后可以顺手把SolidWorks的版本号也写进日志File.AppendAllText( C:\Temp\myaddin.log, SwVersion _app.RevisionNumber() Environment.NewLine);多版本环境部署时这个信息能帮你快速判断用户到底是哪个SolidWorks版本避免拿着2024的插件在2021的机器上排查半天。插件注册这件事原理不复杂但因为跨越了.NET COM、注册表、SolidWorks自身机制三套体系任何一环没接上都会表现得非常玄学。我自己的体会是把项目平台的x64、Regasm的codebase、AddinsStartup的键值类型、旧注册残留清理这四个点挨个确认一遍插件注册阶段能踩的坑基本就都堵上了。当然真想省心最后那行日志永远别删。