ARTICLE DETAIL

资讯详情

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

NX二次开发入门:NX1980+VS2019环境配置与DLL加载全攻略

NX二次开发入门:NX1980+VS2019环境配置与DLL加载全攻略 我最早学着用NX二次开发的时候卡了整整两天不是卡在代码上而是卡在VS工程配置上。包含目录填了又删删了又填老是报LNK2019好不容易把代码编译过了加载进NX又提示“无法加载图像”。那会儿如果有人直接给一套NX1980VS2019从建工程到加载成功的完整样例流程我能少走两周弯路。这篇就是把这条流程完整拆开从环境选型讲到VS工程配置从Journal录制讲到DLL加载最后再给几个实际运营中常踩的坑照着走一遍基本就入门了。这篇内容主要面向两类人一是刚拿到NX1980正版环境、有C基础但不知道从哪下手的开发者二是已经能写简单UFUN程序但没接触过NXOpen C和VS2019配置的老工程师。我默认你会基本的Visual Studio操作但不会假设你懂NX API。整套流程以NX1980VS2019组合为例使用的开发语言是C走的是NXOpen C这条路子。1. 先想明白版本和开发路线再动手装环境1.1 NX1980和VS2019的搭配关系很多人以为NX二次开发就是“装好UG就能写”其实卡住的地方往往在编译器版本上。Siemens在NX1980这个版本里官方支持的Visual Studio是VS2019具体是MSVC v142工具集。NX内部本身是用C/C写的它导出的头文件和动态库在不同版本上有些差异VS版本太新或太旧都可能导致一堆莫名其妙的编译器报错。举个实际例子有人拿VS2022去编译NX1980的插件个别老API会出现“namespace 里找不到符号”或者“cannot convert from X to Y”这种错误因为新版VC的STL实现更严格了而NX的头文件是按老标准写的。VS2017虽然也能用但在有些Windows 10/11新版本上会出现调试器附着不上的问题。所以一个比较稳的组合就是NX1980 VS2019这也是很多企业项目里常见的搭配。如果你装的是更高版本NX比如NX2206或NX2306官方随后已经把VS2022纳入支持范围了但那是另一套配置思路。这篇文章就咬死NX1980VS2019不折腾。1.2 内部开发与外部开发怎么选UG/NX二次开发有两种基本模式内部模式和外部模式。内部模式是把代码编译成一个DLL在NX进程内运行可以访问NX的UI对象、交互选择、视图、Part对象等几乎所有功能也是日常自动化工具最常用的方式。外部模式是编译成一个独立EXE不启动NX界面也能运行适合批处理、服务器端计算这些场景但很多跟界面和交互相关的API用不了。新手入门我强烈建议先走内部模式DLL这条路。原因很简单调试方便能力覆盖面大而且官方大部分示例代码都是内部模式。外部模式涉及的环境变量、会话连接问题更多等把内部模式跑通了再接触也不迟。另外还要提醒一个语言路线的问题。NXOpen API有C、C、C#、Java、Python多种形态。老一辈教程大量使用UFUNUF_开头的那套C接口现在还完全能用但官方在逐步把重心转到NXOpen C上新功能基本都是NXOpen C优先。这篇样例以NXOpen C为例同时也会提到UFUN的入口写法和链接库因为实际项目里经常要混着用。1.3 确认环境变量和目录结构在打开VS2019之前先确认NX安装目录下的这几个东西存在不然后面配置百分之百出问题。打开文件资源管理器找到NX安装根目录通常是C:\Program Files\Siemens\NX1980确认里面有UGII这个子目录。再看UGII目录下有没有这些关键内容ugraf.exeNX图形界面启动程序NXOPEN子目录NXOpen C头文件NXOPENCPP子目录NXOpen C头文件部分版本叫法不同libugopenint.libUFUN和NXOpen C的导入库然后打开命令行输入echo %UGII_ROOT_DIR%。如果环境变量存在会输出类似C:\Program Files\Siemens\NX1980\UGII\这样的路径。如果没有值去Windows系统设置里添加一个变量名UGII_ROOT_DIR变量值就是UGII目录的完整路径带结尾反斜杠。这个环境变量太重要了。VS工程里配包含目录和库目录时全部用$(UGII_ROOT_DIR)宏来引用既能避免路径写死也能保证不同机器上迁移工程时不用改配置。我见过不少同事在另一台电脑上编译失败就是因为他这台机器没设这个变量。2. 让NX自己生成代码Journal录制就是最好的技术顾问2.1 为什么推荐Journal加改造的学习路线很多初学者拿到NXOpen API手册第一反应就是找函数列表然后被几十个命名空间和几千个头文件吓住。NXNXOpen C这套API的类多、方法多、重载也多靠背是背不完的。NX里有一个非常实用的功能叫Journal中文版叫“操作记录”或者“日志”。它的本质是把你在NX界面上的每一步操作翻译成对应语言的API代码。你手动创建一个圆柱体它就生成一段创建圆柱体的代码你手动改一个表达式它就生成修改表达式的代码。这套机制对新手来说就是一本活的API示例库。遇到不会写的功能先手动操作一遍录一段Journal再打开生成代码看它调了哪些类、哪些方法、参数怎么传。这个学习效率比对着帮助文档翻半天高太多了。本文后面的代码样例就是我先用Journal录了一段“创建圆柱体”再把生成的代码改造成标准DLL入口的。这是很多老工程师实际开发时的真实路径绝不是查手册查出来的。2.2 录制一个创建圆柱体的完整操作打开NX1980新建一个空模型文件什么特征都别做。然后按下面的步骤操作。菜单栏选“工具 - 操作记录 - 录制”英文版是Tools - Journal - Record。弹出来一个对话框让你选语言这里务必选CNXOpen C。有的中文版本显示为“Visual C”选它就行。点确定之后NX右上角会出现一个录制中的小提示。这时候开始正常操作菜单栏选“插入 - 设计特征 - 圆柱”英文版是Insert - Design Feature - Cylinder。在弹出的圆柱对话框中直径填50高度填100方向默认Z轴原点保持(0,0,0)。点确定圆柱体出现在模型里。菜单栏选“工具 - 操作记录 - 停止”英文版是Tools - Journal - Stop。停止录制后NX会提示保存Journal文件。你会得到一个.cpp文件名字类似journal_20250101_123456.cpp。用VS2019打开它就能看到刚才那次操作对应的完整API调用代码。这里有个实用小技巧录制之前先把NX界面切到英文界面或者至少记住英文菜单名称这样生成的代码注释和Dialog标题都是英文后续理解起来更顺。中文界面对应的文件编码偶尔会出现乱码虽然不是大问题但影响阅读。2.3 看懂Journal生成代码的结构录出来的代码结构通常长这样NXOpen::Session* theSession NXOpen::Session::GetSession(); NXOpen::Part* workPart theSession-Parts()-Work(); // ... 省略若干行 NXOpen::Features::CylinderBuilder* cylinderBuilder workPart-Features()-CreateCylinderBuilder(nullptr); // 设置圆柱参数 cylinderBuilder-Diameter()-SetRightHandSide(50); cylinderBuilder-Height()-SetRightHandSide(100); // 提交特征 cylinderBuilder-Commit(); // 销毁Builder cylinderBuilder-Destroy();这里有几个关键点要读懂。Session::GetSession()拿到的是当前NX会话的入口对象几乎所有NXOpen操作都要从它开始。theSession-Parts()-Work()拿到的是当前工作Part也就是你现在看到的这个模型文件。Features()-CreateCylinderBuilder(nullptr)创建了一个圆柱体构造器第二个参数传nullptr表示没有参考特征。NXOpen里大量使用这种“Builder模式”创建一个Builder对象设置参数Commit提交最后Destroy释放。可以把它理解成填表申请——你拿了一张申请表Builder一项一项把信息填好Set参数交到窗口Commit申请表作废Destroy。这个模式在NXOpen里几乎无处不在学会看这种结构后面读任何NXOpen代码都不怵。代码里反复出现的SetRightHandSide是设置表达式的方式。NX里很多尺寸参数本质上不是简单数字而是表达式对象。SetRightHandSide(50)等价于在表达式对话框里把右值设成50。传字符串而不是double是因为NX表达式支持“5020”或“p510”这样的复杂写法。3. VS2019项目配置的每个坑我先替你趟一遍3.1 新建工程和基础属性设置打开VS2019创建一个新项目项目类型选“空项目”语言选C项目名称比如NxCylinderSample存放路径建议不要有中文和空格比如D:\NXDev\NxCylinderSample。项目创建好后第一件事是把配置管理器切到x64。NX1980本身是64位程序DLL必须编译成x64否则加载时直接报“模块类型不匹配”。操作路径是菜单栏“生成 - 配置管理器”在“活动解决方案配置”边上下拉框里选“新建”名称随便平台选x64。这里强烈建议直接用Release配置后面我会解释为什么不要纠结Debug。右键项目 - 属性打开属性页。先把“配置”切换成“Release”平台选x64。然后在“配置属性 - 常规”里把“配置类型”从“应用程序(.exe)”改成“动态库(.dll)”。因为NX插件本质就是一个普通DLL只是导出的是ufusr而不是DllMain。3.2 包含目录和库目录的具体配置进入“配置属性 - VC目录”。包含目录添加以下几行$(UGII_ROOT_DIR)UGII $(UGII_ROOT_DIR)NXOPEN $(UGII_ROOT_DIR)NXOPENCPP如果你的NX版本里NXOPENCPP下面还有一个NXOpenCPP子目录也可以加进去。不同小版本目录结构略有不同以实际目录为准。添加完这几行#include NXOpen/NXObject.hxx这种头文件就能被找到了。库目录添加$(UGII_ROOT_DIR)UGIINX的导入库基本都放在UGII目录下。如果你的安装目录里还有单独的UGII\lib之类的子目录同样以实际为准。这里有一个常见的坑有些人配置包含目录时把路径写成了$(UGII_ROOT_DIR)\NXOPENCPP但实际这个目录下没有直接放头文件而是又套了一层子目录。结果就是编译时提示无法打开包含文件NXOpen/NXObject.hxx。如果遇到这种报错打开资源管理器看一眼实际目录结构把路径指到头文件真正所在的那一层就行。3.3 附加依赖项和运行库设置进入“配置属性 - 链接器 - 输入 - 附加依赖项”添加下面这些库。我先给一个最小组合能覆盖本样例代码需求libugopenint.lib libnxopencpp.lib libnxopencpp_annotations.lib libnxopencpp_features.lib这几个库分别是什么作用我做了一个表库文件对应API典型用途libugopenint.libUFUNUF_开头传统C接口文件操作、对象属性、批量处理libnxopencpp.libNXOpen核心C类Session、Part、Expression、NXObject等libnxopencpp_annotations.libNXOpen注释和制图对象工程图标注、尺寸、文本libnxopencpp_features.lib建模特征对象圆柱、长方体、拉伸、孔等特征操作这个列表不是固定死的实际工程里用到什么功能的头文件就追加对应的lib。但新手经常犯的错是把所有lib一股脑全部加进去结果生成时出现一堆重复定义或者版本冲突。更好的做法是先按最小组合来报LNK2019时再根据提示补对应库。接着改“配置属性 - C/C - 代码生成 - 运行库”设为“多线程DLL(/MD)”。这一步非常关键。NX自身是用/MD方式编译的也就是用动态C运行时。如果你的插件用静态运行时(/MT)编译DLL里会带入一套独立的C运行时代码在NX进程里跟NX自己的运行时代码争夺全局状态轻则内存分配异常重则一加载就崩溃。这个坑在UG/NX开发圈非常经典网上搜“UG二次开发崩溃”一半以上是这个原因。最后把“配置属性 - C/C - 预处理器”里的_DEBUG去掉。Debug模式下STL的容器布局和Release不一样而NX的导入库是Release编译的混着用可能导致vector、string这些容器在跨越DLL边界时崩溃。所以NX二次开发我建议直接全程用Release /MD调试用OutputDebugString加日志输出来排查而不是依赖VS的Debug版。这条经验很多人第一次接触时不理解直到自己DEBUG编译的DLL一运行就崩才回头改配置。4. 把Journal代码改造成标准插件DLL4.1 NX插件DLL的入口函数NX加载DLL时不是像普通程序那样找DllMain而是找固定的导出函数。最核心的入口函数是extern C __declspec(dllexport) void ufusr(char* param, int* retCode, int paramLen)这个函数是NX执行插件时的主入口。param是调用时传入的参数字符串retCode是返回码paramLen是参数长度。前面必须加extern C否则C编译器会对函数名做name mangling导出的符号就变成了ufusr...NX根本找不到。另外两个可选但建议实现的函数是extern C __declspec(dllexport) int ufusr_ask_unload(void) { return UF_UNLOAD_UG_TERMINATE; } extern C __declspec(dllexport) void ufusr_terminate(void) { // 资源清理代码写这里 }ufusr_ask_unload返回UF_UNLOAD_UG_TERMINATE表示NX关闭时卸载DLL。也可以返回UF_UNLOAD_SEL_DIALOG让用户从菜单里手动卸载。开发调试阶段建议用UF_UNLOAD_UG_TERMINATE不然你会发现改完代码重新编译后NX里还是旧行为——因为旧DLL一直驻留在内存里没被卸载。这里要说一个很隐蔽的细节ufusr里如果用了任何UFUN函数第一行必须是UF_initialize()最后一行是UF_terminate()否则UFUN函数会返回错误码甚至直接崩溃。如果只用了NXOpen C的API不强制调用UF_initialize。本样例只用NXOpen C所以不需要。但为了以后混用UFUN我习惯性在入口开头判断一下有没有报错。4.2 改造后的完整代码样例下面给出一份可直接使用的完整代码。这段代码是在我录制出的Journal基础上整理出来的去除了Journal自动生成的UI对话框代码保留了核心逻辑同时补上了DLL导出入口。#include NXOpen/NXObject.hxx #include NXOpen/NXException.hxx #include NXOpen/Session.hxx #include NXOpen/Part.hxx #include NXOpen/PartCollection.hxx #include NXOpen/Features_FeatureCollection.hxx #include NXOpen/Features_CylinderBuilder.hxx #include NXOpen/Expression.hxx #include NXOpen/Unit.hxx #include NXOpen/UnitCollection.hxx #include uf.h #include uf_exit.h #define DllExport __declspec(dllexport) extern C DllExport void ufusr(char* param, int* retCode, int paramLen) { int err 0; err UF_initialize(); if (err ! 0) { return; } try { NXOpen::Session* session NXOpen::Session::GetSession(); NXOpen::Part* workPart session-Parts()-Work(); if (workPart nullptr) { return; } // 创建圆柱体构造器 NXOpen::Features::Feature* nullFeature nullptr; NXOpen::Features::CylinderBuilder* cylinderBuilder workPart-Features()-CreateCylinderBuilder(nullFeature); // 设置圆柱参数 // 原点在绝对坐标系原点 NXOpen::Point3d origin(0.0, 0.0, 0.0); cylinderBuilder-Origin()-Move(origin); // 方向朝Z轴 NXOpen::Vector3d direction(0.0, 0.0, 1.0); cylinderBuilder-Direction()-SetValue(direction); // 直径50高度100 cylinderBuilder-Diameter()-SetRightHandSide(50); cylinderBuilder-Height()-SetRightHandSide(100); // 提交并销毁构造器 cylinderBuilder-Commit(); cylinderBuilder-Destroy(); } catch (const NXOpen::NXException ex) { // 实际项目中建议把错误信息写到日志文件 // uc1601是UFUN的弹窗函数可用于显示错误 char msg[256]; sprintf(msg, NXException: %d %s, ex.ErrorCode(), ex.Message()); uc1601(msg, 1); } UF_terminate(); } extern C DllExport int ufusr_ask_unload(void) { return UF_UNLOAD_UG_TERMINATE; } extern C DllExport void ufusr_terminate(void) { }代码不算长但几个关键点值得单独说明。Origin()-Move(origin)和Direction()-SetValue(direction)这两行就是设置圆柱的原点和方向。在Journal代码里Move和SetValue的区别需要注意Origin()返回的是一个可移动点对象所以用MoveDirection()返回的是一个方向对象用SetValue。如果写反了编译阶段可能不报错但运行时会看到圆柱位置或朝向错误。Diameter()-SetRightHandSide(50)这个写法继续沿用了表达式字符串。你完全可以把50替换成5020或者p5NX会像在表达式对话框里一样解析它。这是NXOpen比较有意思的地方——很多参数本质上是表达式。try...catch是必须的。NXOpen API抛出的异常是NXOpen::NXException类型如果不捕获异常会直接越过DLL边界轻则NX弹一个“内部错误”对话框重则直接导致NX进程退出。新手最忌讳的就是把Journal代码无脑塞进ufusr没有任何异常处理一出错整个NX跟着崩。4.3 编译阶段的两个高频错误这个样例在自己电脑上编译最可能遇到两个报错这里先说结论后面的章节再展开排查思路。第一个是LNK2019无法解析的外部符号。这个符号通常是NXOpen::Features::CylinderBuilder::CylinderBuilder或者Session::GetSession这类。解决办法就是在附加依赖项里补lib。报哪个符号就查一下它属于哪个库。一般CylinderBuilder相关属于libnxopencpp_features.libSession相关属于libnxopencpp.lib。第二个是C1083无法打开包含文件NXOpen/NXObject.hxx。这说明包含目录没有指对。去确认一下$(UGII_ROOT_DIR)NXOPEN和$(UGII_ROOT_DIR)NXOPENCPP这两个路径是否真实存在以及中间有没有少反斜杠。5. 加载进NX1980把整个调用链跑通5.1 三种加载DLL的方式编译成功后会生成NxCylinderSample.dll。现在把它加载进NX1980验证整套流程。先把这个DLL拷贝到一个独立目录比如D:\NXDev\bin\NxCylinderSample.dll不建议直接把DLL放在NX安装目录里那样会让NX的安装目录越来越乱也不利于版本管理。第一种加载方式最简单也是最推荐的入门方式菜单栏选“文件 - 执行 - NX Open”英文版是File - Execute - NX Open。弹出对话框后选择D:\NXDev\bin\NxCylinderSample.dll点确定。NX会立即运行ufusr入口函数模型里出现一个直径50、高度100的圆柱体说明整个流程通了。第二种方式是把DLL放到NX的startup目录下比如C:\Program Files\Siemens\NX1980\UGII\menu\startup。下一次启动NX时系统会自动扫描这个目录并加载其中的DLL。这种方式适合做用户环境部署但开发调试阶段不建议因为每次想加载新版DLL必须重启NX。第三种方式是通过MenuScript菜单文件绑定做一个自定义菜单按钮点按钮触发DLL。这种方式更接近正式交付形态第6章展开说。5.2 附加到进程调试DLL代码如果第一次运行就出错或者想完全控制代码走到哪一步就需要用VS2019把调试器附加到NX进程上。先保证NX1980处于打开状态然后回VS2019菜单栏选“调试 - 附加到进程”快捷键CtrlAltP。在进程列表里找到ugs_router.exe或ugraf.exe选中有“托管”或“本机”标记的NX进程点附加。附加成功后在ufusr函数的第一行打个断点。然后回到NX再执行一次“文件 - 执行 - NX Open”加载DLL。VS会立刻中断到断点处接下来就可以像调试普通C程序一样单步跟踪、查看变量了。这里有个小经验VS附加到进程后如果断点显示为空心圆标志表示“尚未加载符号”先别慌。对着代码按F10继续执行等真正运行到相关模块时断点通常会激活。如果一直激活不了检查一下VS的“调试 - 选项 - 调试 - 常规 - 启用本机调试”是不是勾上了以及项目配置是不是Debug版本。还有一个坑是同时开了多个NX进程VS附加错了目标一执行还是没反应——先看进程列表里的PID是不是当前这个NX窗口对应的进程。5.3 运行时报错排查对照表编译过了附加调试也做了但还是弹错误。下面这个表是NX二次开发新手最容易遇到的运行期问题我按照实际踩坑频率排了个序现象直接原因解决思路加载DLL时提示“无法加载图像”DLL位数不对或者缺少运行库确认VS编译平台是x64安装VC 2015-2022运行库加载后没有任何反应ufusr入口没导出或异常被吞掉确认导出函数加了extern C在ufusr第一行加OutputDebugString输出日志NX立即崩溃运行库混用比如/MD和/MT冲突项目属性运行库改为“多线程DLL(/MD)”圆柱方向反了方向向量和预期相反检查Direction()-SetValue传的是不是(0,0,1)负Z方向可传(0,0,1)后再旋转或传(0,0,-1)圆柱位置不对Origin的Move和SetValue用混看Journal生成代码的实际写法和API说明再调整如果你运行后连弹窗都没有我强烈建议在ufusr入口第一行加一个日志输出OutputDebugStringA(NxCylinderSample ufusr entered\n);然后在VS调试输出窗口里看有没有这一行。如果连这行都没有说明DLL根本没被正确加载或入口符号没导出如果能看到这行说明是后续代码出问题再进一步缩小范围。这个排查思路能解决80%的“没反应”问题。5.4 修改代码后热更新的处理方式开发过程里你一定会遇到这个场景第一次加载DLL成功然后你改了代码重新编译再回NX执行加载发现运行还是旧效果甚至弹“模块已加载”之类的提示。原因是NX在本次会话中已经加载过一次DLL同一个进程里同名DLL不会重新加载。最简单的处理办法是关闭NX重新打开再加载新编译的DLL。NXNX1980没有内置的热重载机制别跟它较劲。如果想要开发效率高一点可以在ufusr_ask_unload里返回UF_UNLOAD_SEL_DIALOG这样NX菜单里会出现一个“卸载”选项不用关闭NX就能把DLL从内存里卸掉然后重新加载新版。不过这个选项在部分NX版本里的菜单位置比较隐蔽新手阶段直接重启NX更省心。6. 从一个圆柱体到能交付的功能模块6.1 用Block UI Styler做参数对话框本样例只演示了入口和API调用但实际交付给工程师用的工具几乎都有对话框让用户填参数、选对象。NX1980里做对话框最标准的方式是Block UI Styler。菜单栏选“工具 - Block UI Styler”打开后在左侧控件列表里拖几个控件到画布上比如放一个“数字”控件用来填直径一个“数字”控件用来填高度一个“按钮”控件作为确定键。保存后Block UI Styler会自动生成一个.dlx界面文件、一个.cpp文件和一个.hpp文件。把生成的.cpp和.hpp加入VS工程然后在对话框回调函数里写创建圆柱的逻辑。Block UI Styler生成的代码逻辑比Journal更复杂一些因为它要处理窗口消息循环和回调。需要注意的一点是Block UI Styler生成的代码会自带extern C DllExport入口你在集成时不要跟已有的ufusr重复定义通常是把对话框的初始化过程放到ufusr里调用。6.2 用MenuScript做菜单入口如果想让工程师在NX菜单栏里找到一个自定义按钮点一下就弹对话框就需要MenuScript脚本了。在startup目录下创建一个.men文件比如nx_sample.men内容大致如下EDIT UG_GATEWAY_MAIN_MENUBAR AFTER UG_APPLICATION BUTTON NX_SAMPLE_CYLINDER LABEL 创建圆柱体 ACTIONS NxCylinderSample.dll把编译好的DLL和这个.men文件都放到startup目录重启NX菜单栏里就会出现“创建圆柱体”按钮。这个脚本语法在NX1980里是通用的网上能搜到很多变体核心就是BUTTON定义按钮IDLABEL定义显示文字ACTIONS绑定要加载的DLL文件名。6.3 后面可以往哪些方向扩展本样例是以“创建圆柱体”为最小闭环但它覆盖的技术链路——配置环境、Journal录制、代码改造、DLL编译、NX加载、调试排错——是通用的。你可以把这套流程迁移到其他功能上比如批量给选中的体赋材质或属性就用UFUN的UF_ATTR_assign配合选择对象。读取Excel里的坐标点批量创建点或曲线用NXOpen的Points集合和Excel COM接口。自动导出工程图PDF走NXOpen的PlotManager。截图当前视图用UF_VIEW_ask_xyzt拿到视图矩阵再结合图形接口。这些功能本质上都是“进工程、写API代码、编译DLL、加载运行”四步区别只在于调用了哪些API。所以这篇样例最核心的价值不是圆柱体本身而是让你把手感练出来知道完整的链路长什么样以后再遇到具体需求就能按同样的套路拆解。就我个人的体会NX二次开发最近这几年最大的变化不是API变得多复杂而是学习路径变得更清晰了。Journal就是最好的老师不知道怎么写就先录一遍编译集成有问题就优先怀疑运行库和平台位数运行期出问题就加日志缩小范围。按这个路子走不敢说你能成为NX开发大牛但至少面对NX1980VS2019这套环境时不会再有那种无从下手的感觉。后面再遇到具体API问题翻帮助文档、查示例都会更有方向。
返回列表