ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SolidWorks二次开发实战:基于VC++的插件开发与API调用详解

SolidWorks二次开发实战:基于VC++的插件开发与API调用详解 1. 项目概述为什么SolidWorks二次开发值得投入如果你是一名机械设计工程师或者是在制造业、自动化设备领域摸爬滚打多年的技术负责人那么SolidWorks这个名字对你来说一定不陌生。作为三维CAD设计领域的标杆之一它几乎成了我们日常工作中不可或缺的工具。但你是否也遇到过这样的场景一个复杂的装配体需要批量修改上百个零件的特定属性或者一个标准化的设计流程每次都要重复几十次枯燥的点击操作又或者公司内部有一套独特的设计规范和BOM表格式需要从SolidWorks模型中自动提取并生成报告。这些重复、繁琐但又至关重要的任务正是SolidWorks二次开发大显身手的地方。我接触SolidWorks二次开发已经超过十年从最早的宏录制到后来的VB.NET再到深入使用VC即Visual C进行底层API调用可以说踩遍了所有的坑也尝遍了所有的甜头。今天我想和你深入聊聊基于VC的SolidWorks二次开发。选择VC并不是因为它最简单——恰恰相反它可能是入门门槛最高的路径之一。但它的优势在于执行效率高、与Windows系统及SolidWorks底层COM接口结合紧密、生成的插件稳定且性能强悍特别适合开发需要复杂计算、高频次调用API或对稳定性要求极高的企业级应用。网上关于SolidWorks二次开发的资料不少但成体系的、特别是针对VC的示例代码和深度解析却不多见。很多朋友拿到官方庞大的API文档和寥寥几个示例后往往不知从何下手。这篇文章我将从一个完整的、可运行的VC示例项目出发拆解其背后的每一个技术细节、设计思路和实操要点。我的目标不是让你“照猫画虎”地运行一个Demo而是帮你建立起一套从环境搭建、项目创建、代码编写、调试到最终部署的完整知识体系让你能真正理解原理并具备独立开发定制化工具的能力。2. 开发环境准备与项目创建2.1 工具链选型与配置逻辑工欲善其事必先利其器。在开始敲代码之前搭建一个稳定、高效的开发环境是第一步也是最容易出问题的一步。很多新手卡在第一步往往是因为版本不匹配。1. 核心工具选择SolidWorks版本这是基石。我强烈建议你选择一款相对稳定且普及的版本例如SolidWorks 2020或2022。太老的版本如2016以前其API可能不够完善或已被弃用太新的版本如2024可能遇到第三方库或教程支持不足的问题。关键原则是你的开发环境SolidWorks版本必须与目标用户最终使用的版本一致或兼容。SolidWorks的API在不同大版本间可能有变动向下兼容性尚可但向上兼容无法保证。Visual Studio版本这是我们的“车间”。SolidWorks二次开发主要基于COM技术因此我们需要使用支持COM开发的VC环境。对于SolidWorks 2020推荐使用Visual Studio 2019对于SolidWorks 2022则推荐Visual Studio 2022。VS的版本与VC编译器版本紧密相关匹配使用能最大程度避免奇怪的编译或链接错误。Windows SDK确保安装的Windows SDK版本与你的Visual Studio版本匹配。通常在安装VS时会自动安装推荐的SDK。2. 获取官方SDKSolidWorks为开发者提供了完整的API库和头文件。你需要从SolidWorks客户门户或官方开发者网站下载对应版本的SolidWorks API SDK。这个SDK包里包含了最重要的几个目录samples官方示例代码是我们学习的宝贵资源。include所有的API头文件.h例如swdocumentmgr.h,sldworks.h等。lib或redist编译所需的导入库文件.lib和运行时库.dll。注意将SDK中的include和lib路径正确添加到你的Visual Studio项目属性中是项目能否成功编译链接的关键。后面我们会详细操作。2.2 创建第一个VC插件项目我们从一个最经典的“Hello World”式插件开始创建一个SolidWorks菜单项点击后弹出一个消息框。这个简单的例子涵盖了插件生命周期的核心。步骤一在Visual Studio中创建新项目打开Visual Studio选择“创建新项目”。在项目模板中搜索并选择“Windows桌面向导”给项目起个名字例如SwAddinDemo。在接下来的“应用程序类型”中选择“动态链接库(.dll)”。因为SolidWorks插件本质上就是一个实现了特定COM接口的DLL文件。在“附加选项”中勾选“导出符号”和“空项目”。我们不需要预编译头等额外内容保持项目干净。步骤二配置项目属性关键步骤项目创建后右键点击项目名称选择“属性”进行如下配置C/C - 常规 - 附加包含目录添加你下载的SolidWorks SDK中的include文件夹路径。例如C:\SolidWorks SDK\include。这样编译器才能找到sldworks.h等头文件。链接器 - 常规 - 附加库目录添加SolidWorks SDK中的lib文件夹路径。例如C:\SolidWorks SDK\lib。链接器 - 输入 - 附加依赖项添加必须的库文件。通常至少需要sldworks_i.lib和swconst_i.lib。前者包含了SolidWorks主程序的接口定义后者包含了各种常量定义。C/C - 预处理器 - 预处理器定义添加_WIN32_DCOM。这是为了启用分布式COM支持确保插件在COM环境下正常运行。完成这些配置你的VC项目才算和SolidWorks API建立了连接。3. 插件框架与COM接口深度解析3.1 理解SolidWorks插件的COM本质要玩转VC二次开发必须过COM这一关。你可以把COMComponent Object Model想象成一套严格的“通信协议”。SolidWorks主程序作为COM客户端和我们的插件DLL作为COM服务器通过这套协议进行对话。对于SolidWorks插件我们需要实现几个核心的COM接口IUnknown所有COM对象的根基提供引用计数和接口查询功能。在VC中我们通常通过继承来实现它。IDispatch支持自动化如VBA宏调用的接口。如果希望你的插件也能被宏调用就需要实现它。对于纯VC插件有时可以省略。SwAddin或SwDll这是SolidWorks自定义的接口用于管理插件的生命周期。这是我们必须实现的核心接口。它定义了ConnectToSW和DisconnectFromSW两个关键方法分别在SolidWorks加载和卸载插件时被调用。在代码中我们通过一个C类来同时实现这些接口。例如class CSwAddinDemo : public SwAddin { public: // IUnknown 方法 STDMETHOD(QueryInterface)(REFIID riid, void** ppvObj); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release)(); // SwAddin 方法 STDMETHOD(ConnectToSW)(LPSLDWORKS pSw, LONG cookie); STDMETHOD(DisconnectFromSW)(); private: LPSLDWORKS m_pSldWorks; // SolidWorks应用程序对象指针 LONG m_Cookie; // 连接标识符 ULONG m_RefCount; // 引用计数 };3.2 插件生命周期与事件管理ConnectToSW方法是插件的“入口函数”。当SolidWorks启动并加载你的插件DLL时会调用这个方法并传入两个至关重要的参数pSw这是一个指向ISldWorks接口的指针。这是你与SolidWorks世界交互的“总钥匙”。通过它你可以获取当前打开的文档、创建命令、访问属性管理器几乎可以做任何事情。你需要将这个指针安全地保存到类的成员变量中如上面的m_pSldWorks。cookie一个长整型的连接标识符。在后续调用某些SolidWorks API如添加菜单时需要提供这个cookie来证明你的插件身份。同样需要保存。在ConnectToSW方法中我们通常会做以下几件事保存pSw和cookie。增加引用计数AddRef表示我们持有了这个接口指针。调用pSw-AddCommandItem等API向SolidWorks的菜单栏、工具栏或右键菜单添加我们自定义的命令按钮。可选地订阅SolidWorks的事件如文档打开、关闭、重建事件。这需要用到IConnectionPoint等更高级的COM技术。DisconnectFromSW方法是插件的“清理函数”。当SolidWorks关闭或用户卸载插件时被调用。在这里你必须删除所有添加的菜单项、工具栏按钮。取消所有的事件订阅。释放之前保存的m_pSldWorks指针调用Release方法。将成员指针置为NULL防止野指针。实操心得资源管理是VC插件稳定性的生命线。务必遵循“谁申请谁释放”的原则。在ConnectToSW中AddRef的指针必须在DisconnectFromSW中Release。任何疏忽都可能导致SolidWorks在关闭时崩溃或内存泄漏。我习惯在调试版本中在析构函数里加入断言assert检查所有指针是否已被安全释放。4. 核心功能实现从菜单到模型交互4.1 创建用户界面菜单与工具栏让用户能触发我们的功能最直接的方式就是添加一个菜单项。下面是一个在SolidWorks主菜单栏添加自定义菜单组的典型代码片段通常写在ConnectToSW方法中HRESULT CSwAddinDemo::ConnectToSW(LPSLDWORKS pSw, LONG cookie) { HRESULT hr S_OK; m_pSldWorks pSw; m_Cookie cookie; m_pSldWorks-AddRef(); // 重要增加引用计数 // 1. 获取命令管理器接口 ICommandManager* pCmdMgr NULL; hr m_pSldWorks-GetCommandManager(pCmdMgr); if (FAILED(hr) || !pCmdMgr) return hr; // 2. 创建一个命令组可以包含多个命令 long cmdGroupId -1; hr pCmdMgr-CreateCommandGroup2( m_Cookie, // 我们的插件cookie _T(我的工具组), // 组标题菜单上显示的名称 _T(我的工具组提示), // 工具提示 _T(我的工具组), // 内部标识 -1, // 位置索引-1表示在末尾添加 TRUE, // 是否立即显示 cmdGroupId // 返回的组ID后续需要保存 ); if (FAILED(hr)) { pCmdMgr-Release(); return hr; } // 3. 在组内添加一个具体的命令项 long cmdIndex 0; // 命令在组内的索引 long cmdId -1; // 命令的唯一ID hr pCmdMgr-AddCommandItem2( cmdGroupId, // 所属组ID cmdIndex, // 索引 _T(执行我的功能), // 命令文本 _T(这是一个示例功能的详细提示), // 状态栏提示 swMenuItem, // 项目类型菜单项 m_Cookie, // 插件cookie _T(MyCommandCallback), // 回调函数标识符需要注册 NULL, // 图标文件路径可选 NULL, // 次要图标可选 NULL, // 提示栏位图可选 cmdId // 返回的命令ID ); if (FAILED(hr)) { /* 错误处理 */ } // 4. 激活命令组使其可见 hr pCmdMgr-ActivateCommandGroup2(m_Cookie, cmdGroupId); pCmdMgr-Release(); // 释放命令管理器接口 return hr; }这段代码的关键在于AddCommandItem2中的_T(MyCommandCallback)参数。这是一个字符串标识符它告诉SolidWorks当用户点击这个菜单项时应该去调用插件中哪个函数来处理。因此我们必须在插件中注册这个回调函数。这通常通过实现一个特定的接口如ICommandHandler并在ConnectToSW中调用pCmdMgr-RegisterCommandHandler来完成。回调函数里就是执行我们核心业务逻辑的地方。4.2 访问与操作SolidWorks文档对象用户点击菜单后我们的回调函数被触发。此时我们通常需要获取当前活动的文档并对其进行操作。这是二次开发最核心的部分。// 假设这是命令回调函数的一部分 void CSwAddinDemo::OnMyCommand() { if (!m_pSldWorks) return; // 1. 获取当前活动文档 IModelDoc2* pModelDoc NULL; HRESULT hr m_pSldWorks-get_IActiveDoc2(pModelDoc); if (FAILED(hr) || !pModelDoc) { MessageBox(NULL, _T(请先打开或激活一个SolidWorks文档), _T(提示), MB_OK); return; } // 2. 判断文档类型零件、装配体、工程图 long docType; pModelDoc-GetType(docType); CString msg; switch (docType) { case swDocPART: msg _T(当前是零件文档。); // 这里可以调用零件特有的API如获取特征树、修改特征参数 break; case swDocASSEMBLY: msg _T(当前是装配体文档。); // 这里可以调用装配体特有的API如遍历组件、获取配合关系 break; case swDocDRAWING: msg _T(当前是工程图文档。); // 这里可以调用工程图特有的API如操作视图、标注 break; default: msg _T(未知文档类型。); } // 3. 获取文档的路径和名称示例 BSTR docPath; pModelDoc-GetPathName(docPath); CString strPath(docPath); SysFreeString(docPath); // 重要释放BSTR内存 msg _T(\n文档路径) strPath; MessageBox(NULL, msg, _T(文档信息), MB_OK); // 4. 示例获取文档中所有配置的名称 IConfiguration* pConfig NULL; hr pModelDoc-GetActiveConfiguration(pConfig); if (SUCCEEDED(hr) pConfig) { BSTR configName; pConfig-get_Name(configName); CString strConfigName(configName); SysFreeString(configName); // ... 处理配置信息 pConfig-Release(); } // 5. 最后释放文档接口 pModelDoc-Release(); }这段代码展示了与文档交互的基本模式获取接口指针 - 调用方法 - 处理返回结果尤其是BSTR字符串的内存释放- 释放接口指针。对BSTR和COM接口指针的及时释放是避免内存泄漏的重中之重。4.3 遍历特征树与修改参数一个更进阶的例子是遍历零件文档的特征树并修改特定特征的参数。这常用于批量修改、设计自动化等场景。void CSwAddinDemo::TraverseAndModifyFeatures(IModelDoc2* pModelDoc) { // 1. 获取特征管理器对象 IFeatureManager* pFeatMgr NULL; HRESULT hr pModelDoc-FeatureManager(pFeatMgr); if (FAILED(hr) || !pFeatMgr) return; // 2. 获取第一个特征 IFeature* pFeature NULL; hr pFeatMgr-FirstFeature(pFeature); while (SUCCEEDED(hr) pFeature ! NULL) { // 3. 获取特征定义类型 long featType; pFeature-GetTypeName2(featType); // 4. 判断是否为拉伸特征示例 if (featType swFeatExtrusion) { // 5. 获取特征的具体定义接口 IExtrusionFeatureData* pExtData NULL; hr pFeature-GetDefinition(pExtData); if (SUCCEEDED(hr) pExtData) { // 6. 修改拉伸深度例如统一设置为50mm // 注意修改参数前通常需要先开始编辑StartEditing pModelDoc-EditRebuild3(); // 确保模型在可编辑状态 // 假设我们要修改深度参数“D1” hr pExtData-SetDepth(50.0 / 1000.0); // API通常使用米为单位50mm需转换 if (SUCCEEDED(hr)) { // 7. 修改后需要通知模型重建 pFeature-ModifyDefinition(pExtData, pModelDoc, NULL); } pExtData-Release(); } } // 8. 获取下一个特征继续遍历 IFeature* pNextFeature NULL; hr pFeature-GetNextFeature(pNextFeature); pFeature-Release(); // 释放当前特征 pFeature pNextFeature; // 移动到下一个 } // 9. 重建整个模型使修改生效 pModelDoc-EditRebuild3(); pFeatMgr-Release(); }注意事项遍历和修改特征树是高风险操作。务必在操作前考虑特征间的父子关系和重建顺序。一个特征的修改可能导致依赖它的后续特征失败。强烈建议在修改前先调用pModelDoc-SetSaveFlag(FALSE)标记文档为“未保存”状态这样如果操作导致严重错误用户可以选择不保存。对于复杂的批量修改最好先在一个备份文档上测试或者提供“撤销”功能。5. 高级主题属性访问、事件响应与调试部署5.1 读写自定义属性与配置特定属性除了几何特征文档和模型的自定义属性Custom Properties也是二次开发中频繁操作的对象。这些属性可以存储在文件级、配置级或模型级。void CSwAddinDemo::ReadWriteCustomProperty(IModelDoc2* pModelDoc) { // 1. 获取自定义属性管理器 ICustomPropertyManager* pPropMgr NULL; // 获取当前配置的属性管理器swCfgPropertyConfiguration HRESULT hr pModelDoc-Extension-CustomPropertyManager(pModelDoc-ConfigurationManager.ActiveConfiguration.Name, pPropMgr); // 如果要获取文件级属性传入空字符串CustomPropertyManager(, pPropMgr) if (SUCCEEDED(hr) pPropMgr) { // 2. 读取一个属性值 BSTR propName SysAllocString(L零件号); // 属性名 BSTR propVal; BSTR resolvedVal; long wasResolved; hr pPropMgr-Get5(propName, TRUE, propVal, resolvedVal, wasResolved); if (SUCCEEDED(hr)) { CString strVal(propVal); // 处理读取到的值... SysFreeString(propVal); SysFreeString(resolvedVal); } SysFreeString(propName); // 3. 写入或修改一个属性值 propName SysAllocString(L材料); BSTR propText SysAllocString(L6061铝合金); hr pPropMgr-Set2(propName, propText); // Set2方法会自动创建或更新属性 if (FAILED(hr)) { // 处理错误例如属性名可能包含非法字符 } SysFreeString(propName); SysFreeString(propText); // 4. 遍历所有属性 VARIANT varNames; VariantInit(varNames); hr pPropMgr-GetNames(varNames); if (SUCCEEDED(hr) varNames.vt (VT_ARRAY | VT_BSTR)) { SAFEARRAY* psa varNames.parray; BSTR* pbstr; SafeArrayAccessData(psa, (void**)pbstr); long lBound, uBound; SafeArrayGetLBound(psa, 1, lBound); SafeArrayGetUBound(psa, 1, uBound); for (long i lBound; i uBound; i) { CString strName(pbstr[i]); // 对每个属性名进行处理... } SafeArrayUnaccessData(psa); } VariantClear(varNames); pPropMgr-Release(); } }操作自定义属性时要特别注意BSTR字符串的内存管理和VARIANT变量的初始化和清理VariantInit和VariantClear。这些是COM编程中常见的错误来源。5.2 响应SolidWorks事件让插件能够响应SolidWorks内部发生的事件如打开文档、保存文档、重建模型可以开发出非常智能的工具比如自动备份、设计变更日志、实时检查等。实现事件响应相对复杂需要以下步骤定义事件接收器类创建一个C类继承自SolidWorks事件接口如DswDocumentEvents,DswPartEvents等。这些接口在SDK的swdocumentevents.h等头文件中定义是一组纯虚函数。实现事件方法在接收器类中实现你关心的事件方法例如OnFileOpenNotify,OnDestroyNotify,OnRebuildNotify。建立连接点在插件的ConnectToSW方法中使用COM的IConnectionPointContainer和IConnectionPoint接口将你的事件接收器对象“建议”Advise给SolidWorks。处理事件当事件发生时SolidWorks会调用你实现的方法。由于代码较长这里简述核心流程你需要创建一个实现了DswDocumentEvents的类CDocEventHandler并在其中重写OnDestroyNotify等方法。然后在主插件类中创建该事件处理器的实例并通过FindConnectionPoint和Advise将其连接到SolidWorks的文档事件源上。实操心得事件处理是提升插件“智能”程度的关键但也容易引入性能问题和稳定性风险。务必确保事件处理函数执行速度极快避免进行复杂的同步操作或弹出模态对话框否则会严重阻塞SolidWorks主线程导致界面卡死。对于耗时操作应该启动一个后台线程来处理。同时在插件断开连接时DisconnectFromSW必须记得调用Unadvise来断开所有事件连接。5.3 调试技巧与插件部署调试调试VC插件与调试普通DLL不同因为宿主进程是SolidWorks。在Visual Studio中将项目属性中的“调试”-“命令”设置为你的SolidWorks主程序路径例如C:\Program Files\SolidWorks Corp\SolidWorks\SLDWORKS.exe。在“调试”-“命令参数”中可以添加/r参数来重置SolidWorks注册表这在插件注册出错时非常有用但会清除所有用户设置慎用。在你的代码中设置断点然后按F5启动调试。Visual Studio会启动SolidWorks当SolidWorks加载你的插件并执行到断点时就会中断。部署开发完成后你需要将插件分发给用户。注册DLLSolidWorks插件是COM服务器必须先在系统注册表中注册。通常创建一个.reg文件或安装程序来完成。关键是在注册表的HKEY_CLASSES_ROOT\CLSID\{你的插件GUID}\InprocServer32下指定你的DLL路径。SolidWorks加载注册后用户需要在SolidWorks的“工具”-“插件”或“附加组件”对话框中勾选你的插件名称SolidWorks才会在启动时加载它。你也可以通过修改注册表让插件在SolidWorks启动时自动加载。依赖项确保目标机器上安装了与你开发环境相同版本的Visual C可再发行组件包如VC 2019 Redistributable。否则你的DLL可能无法运行。版本兼容性如前所述插件DLL与SolidWorks主程序的版本绑定很紧。为不同版本的SolidWorks分别编译和分发插件是常见做法。可以通过在代码中动态查询ISldWorks接口的版本来做一些简单的兼容性判断。6. 常见问题排查与性能优化6.1 编译与链接错误LNK2001: 无法解析的外部符号这是最常见的问题几乎总是因为链接器没有找到正确的.lib文件。请再次检查“项目属性 - 链接器 - 输入 - 附加依赖项”中是否包含了sldworks_i.lib和swconst_i.lib并且“附加库目录”路径正确。C1083: 无法打开包括文件编译器找不到头文件。检查“附加包含目录”设置确保路径指向SDK的include文件夹且路径中无中文或特殊字符。COM接口调用返回失败HRESULT错误使用SUCCEEDED(hr)或FAILED(hr)判断每次API调用的结果。可以使用FormatMessage函数将HRESULT转换为可读的错误信息。更多时候失败是因为传入的参数不对如NULL指针、对象状态不对如文档未处于编辑状态或接口指针已失效。6.2 运行时崩溃与稳定性SolidWorks启动时崩溃大概率是插件DLL的DllMain函数或ConnectToSW函数中出现了严重错误如访问违规、未处理的异常。使用Visual Studio附加到SolidWorks进程进行调试查看崩溃点的调用堆栈。内存泄漏这是VC插件的顽疾。务必确保每一个通过API获取的接口指针以p开头如pModelDoc和BSTR字符串在使用完毕后都调用Release()或SysFreeString()释放。可以使用Visual Studio的内存泄漏检测工具_CrtDumpMemoryLeaks在调试版本中辅助排查。插件命令不显示或灰色检查CreateCommandGroup2和ActivateCommandGroup2的返回值。确保cookie值传递正确。有时菜单加载有延迟重启SolidWorks试试。6.3 性能优化建议减少API调用频率每次跨COM边界调用都有开销。例如在遍历几百个特征时不要在每个特征内部都去调用pModelDoc-GetTitle()这样的方法。尽可能一次性获取所需数据或在本地缓存。批量操作与事务对于需要修改大量数据的操作如批量修改属性如果API支持尽量使用批量处理的方法。某些操作可以通过开启一个“事务”虽然SolidWorks API没有显式的事务接口来减少中间重建的次数即先抑制重建所有修改完成后再一次性重建。善用选择集与过滤器当需要用户交互选择某个面、边线或特征时使用IEntity接口和选择过滤器比遍历整个模型然后让用户去点选要高效和准确得多。异步与多线程对于极其耗时的操作如复杂计算、网络请求务必放在后台线程中执行避免阻塞SolidWorks主UI线程。但要注意绝大部分SolidWorks API对象都不是线程安全的必须在主线程中调用。后台线程可以通过Windows消息或回调函数将结果传回主线程再由主线程调用API进行模型更新。开发SolidWorks VC插件是一条陡峭但回报丰厚的路。它要求你不仅懂C和COM还要深刻理解SolidWorks的数据模型和操作逻辑。从一个小功能开始逐步迭代积累自己的代码库和工具集你会发现它极大地解放了生产力并能解决那些通用软件无法应对的特定业务难题。希望这篇长文能成为你探索之路上的一个扎实的起点。如果在实践中遇到具体问题多查阅官方API帮助文档善用调试工具社区的讨论也常常能带来启发。
返回列表