中望CAD二次开发实战:C++与QT结合实现批量导出图元信息

发布时间:2026/9/2 3:25:37
中望CAD二次开发实战:C++与QT结合实现批量导出图元信息 简介一套面向中望CAD二次开发入门者的C示例工程配有Qt对话框界面可读取扩展字典适用于需在ZWCAD 2022中集成自定义工具的开发者。整套工程基于VS2015与Qt 5.12.9构建并可通过少量调整套用到AutoCAD具备很强的移植参考价值。压缩包共273个文件、容量约206.42MB包含dll动态库、lib导入库、zrx插件模块、cpp/h源码文件、vcxproj工程配置、ui界面定义及moc生成的元对象代码等从源码到编译产物链完整便于对照理解Qt界面嵌入与扩展字典读写机制。目录中还有调试符号pdb、日志与生成状态文件可辅助排查编译和加载阶段的问题。目前已有2804人浏览学习适合具备基础C知识、希望快速上手CAD二次开发并掌握QT对话框集成方法的读者。 中望CAD的二次开发C 和 QT 的组合猛一看像是个“又要啃 SDK又要搞界面”的硬骨头但实际做完这个项目之后我的评价就六个字简单但真能干活。如果你之前接触过 ObjectARX中望的 ZRX 二次开发几乎是把类名从 AcDb 换成了 ZcDb迁移成本低得让人意外而 QT 界面这块只要绕开“在宿主进程里跑 Qt”的几个经典坑用户体验可以做得比纯命令行、比 LISP 对话框舒服很多。这篇文章我就把这个项目从环境搭建到核心功能实现、再到发布部署的完整过程拆给你看。内容包括 ZRX 工程怎么配、入口函数怎么写、QT 对话框怎么在 CAD 进程里正常弹出来、以及一个完整的“批量导出图元信息到 CSV”功能实例。适合两类人看一类是准备接中望 CAD 插件开发、但还没摸清套路的 C 工程师另一类是本来做 ObjectARX想快速迁移到 ZRX 的同学。1. 先搞清楚这个项目到底在做什么1.1 这类需求在中望CAD生态里很常见中望CAD 现在在企业里的覆盖率不低很多设计院、制造企业都把它当作主力绘图工具。CAD 用多了就一定会碰到一个共性诉求标准图框批量生成、图纸信息自动提取、图元属性批量修改、对接 ERP/PLM 系统。这些靠手点命令不现实靠 LISP 写脚本又顶不住复杂的数据处理和界面交互于是“插件化二次开发”就成了唯一靠谱的出路。我接到的这个项目需求很典型用户希望在中望CAD 里加一个自己的功能按钮点开后弹出一个自定义面板通过面板上的参数控制一些批处理操作最后把结果导出成文件。界面用 QT 做核心逻辑用 C 写最终产物是一个可以在中望CAD 里用 APPLOAD 加载的插件模块。听起来挺大但拆开之后每个环节都有成熟套路。1.2 为什么偏偏是“C QT”这个组合很多人在选型时会纠结中望CAD 不是也支持 .NET 二次开发吗不是也能用 LISP 吗为什么还要上 C 和 QT我的理解是这样LISP 适合写几十行的小工具做一个带多个控件、有文件交互、有进度反馈的对话框它做起来很痛苦而且性能没保证。.NET 插件开发效率确实高WinForms 或 WPF 界面也方便但在部分企业环境里运行库版本、权限策略会出现各种莫名其妙的问题而且遇到大图遍历、底层算法介入的时候托管代码的腰杆不够硬。C 加 ZRX 的好处很直接性能顶得住底层控制力强ZRX 接口本身跟 ObjectARX 几乎同构网上大量 ObjectARX 经验可以直接平移过来。至于 QT它在这里的角色不是“整个应用框架”而是纯粹的“界面组件库”。我们不需要 QMainWindow不需要 QApplication 的主事件循环只需要把 QDialog 拿出来在 CAD 的命令线程里弹一个模态对话框。这个用法很轻但非常实用也正好呼应了标题里那句“简单但有效”。1.3 整体架构一句话说明整个插件就三层ZRX 入口层负责向 CAD 注册命令和接收事件中间业务层用纯 C 操作 ZcDb 数据库对象完成选择集、图元遍历、属性读取界面层用 QT Widgets 做对话框负责接收用户参数和反馈结果。三者之间通过普通函数调用连接不引入复杂的插件框架不搞微服务式的解耦。对于一个团队内部工具这种“能干活就行”的架构是最稳的。2. 环境准备把ZRX工程模板整明白2.1 该准备哪些开发物料做 ZRX 开发不需要太多东西四样就够了中望CAD 本体、对应版本的 ZRX SDK、Visual Studio、QT 开发库。SDK 在中望CAD 官网开发者社区就能下载下载时注意版本要跟你装的 CAD 版本匹配比如中望CAD 2023 就用它对应年份的 SDK千万别混用否则编译时头文件里的接口都对不上或者就算编译过了加载的时候也容易崩。VS 版本也要对齐。不同年份的 ZRX SDK 对 VS 版本有明确要求有的是 VS2015有的是 VS2017有的支持 VS2022。这种限制看着烦但实际是有道理的CAD 内部用的是特定版本的 MSVC 运行时插件用不同版本的运行时跟它混跑轻则告警重则直接内存布局不匹配。所以第一步就是打开 SDK 自带的 ReadMe 或者示例工程确认编译器和工具集版本。QT 这边我建议直接用动态库方式不用静态编译。原因后面发布章节会说。版本上 5.12、5.15 这类 LTS 版本足够稳定没必要追新。2.2 新建工程时最容易配错的三处SDK 里有现成的 ZRX 示例工程建议不要自己从空工程开始搭直接复制示例改名字是最省事的。我拿到工程后只改了三个地方就开始写代码了。第一是输出名称和后缀。ZRX 插件的最终产物不是 DLL 而是 .zrx 文件其实本质还是 DLL只是扩展名不同。在工程属性里把目标文件名改成你的插件名扩展名改成 .zrx 就行。第二是附加包含目录和附加库目录。把 SDK 的 inc 和 lib 路径加进去再把 QT 的 include 和 lib 路径也加进去。QT 这边如果你用默认安装路径可以直接用环境变量引用比如$(QTDIR)\include省得换机器就要改路径。第三是字符集。工程属性里字符集必须选 Unicode这跟 CAD 内部使用宽字符有关。如果你用多字节字符集后面凡是涉及中文提示、图元名读取的地方全都会变成乱码甚至编译报错。2.3 入口函数让CAD知道“你的插件活着”ZRX 插件的入口函数是类似 zxdsysInitialize 这样的导出函数CAD 在加载插件时会调用它。你的命令注册、菜单注册、卸载清理逻辑都挂在这里。不同版本 SDK 的函数名可能有差异我用的版本入口是这样的extern C AcRx::AppRetCode zxdsysInitialize(AcRx::AppMsgCode msg, void* pkt) { switch (msg) { case AcRx::kInitAppMsg: acrxDynamicLinker-unlockApplication(pkt); acrxRegisterAppMDIAware(pkt); zceditCommand(_T(EXPORT_INFO), exportInfoCommand); break; case AcRx::kUnloadAppMsg: acrxDynamicLinker-detachApplication(pkt); break; } return AcRx::kRetOK; }这里面的zceditCommand是 ZRX 的命令注册宏第一个参数是你在命令行里敲的命令名第二个参数是对应的 C 函数。加载插件之后在 CAD 命令行输入 EXPORT_INFO就会执行到exportInfoCommand函数。如果你看到这里觉得跟 ObjectARX 的acedRegCmds-addCommand很熟那我的判断没错ZRX 就是把那套机制包了一层更简洁的宏。提示如果你的 SDK 版本里找不到 zxdsysInitialize去 SDK 的 doc 目录搜 zxdInit 或者 zxdsys 关键字一定能找到对应入口函数名。不同版本命名差异是正常的不用慌。3. 让QT对话框在CAD进程里正常弹出来3.1 在宿主进程里初始化QT的正确姿势这是整个项目里最容易被坑翻车的地方。CAD 本身不是 QT 程序它的主线程有自己的消息循环而 QT 的对话框需要 QApplication 实例才能工作。你不能在插件加载时就盲目 new 一个 QApplication那样会使 QT 尝试接管一部分程序级状态搞不好跟 CAD 冲突。我的做法是把 QApplication 的初始化延迟到第一次弹对话框时并且只初始化一次之后绝不 delete。代码写成一个工具函数void initQtIfNeeded() { static bool inited false; if (inited) return; int argc 0; new QApplication(argc, nullptr); inited true; }QApplication构造时不需要非得有命令行参数传 0 和 nullptr 是合法的QT 内部会自己去初始化平台插件。这样 CAD 还是 CADQT 只是作为“零件”嵌在进程里双方各跑各的逻辑。3.2 “选择文件”对话框的实现与运行库坑QT 对话框最常见的一个功能就是选择文件热搜词里也有一条 qt 弹出对话框选择文件说明很多人卡在这儿。代码本身不复杂void initQtIfNeeded(); static bool showExportDialog(QString outPath) { initQtIfNeeded(); QDialog dlg; dlg.setWindowTitle(QStringLiteral(批量导出图元信息)); QVBoxLayout* mainLayout new QVBoxLayout(dlg); QHBoxLayout* pathLayout new QHBoxLayout(); QLineEdit* pathEdit new QLineEdit(dlg); QPushButton* browseBtn new QPushButton(QStringLiteral(浏览...), dlg); pathLayout-addWidget(pathEdit, 1); pathLayout-addWidget(browseBtn); QPushButton* startBtn new QPushButton(QStringLiteral(开始导出), dlg); mainLayout-addLayout(pathLayout); mainLayout-addWidget(startBtn); QObject::connect(browseBtn, QPushButton::clicked, []() { QString fileName QFileDialog::getSaveFileName( dlg, QStringLiteral(保存CSV文件), QStringLiteral(export.csv), QStringLiteral(CSV Files (*.csv))); if (!fileName.isEmpty()) pathEdit-setText(fileName); }); QObject::connect(startBtn, QPushButton::clicked, []() { if (pathEdit-text().isEmpty()) { QMessageBox::warning(dlg, QStringLiteral(提示), QStringLiteral(请先选择CSV文件保存路径。)); return; } dlg.done(QDialog::Accepted); }); if (dlg.exec() ! QDialog::Accepted) return false; outPath pathEdit-text(); return true; }这段代码直接可以跑。但这里藏了一个非常经典的发布坑QFileDialog 是跨平台的它在 Windows 上需要一个叫 platforms/qwindows.dll 的 QT 平台插件才能弹出来。如果发布时只拷贝了 Qt5Widgets.dll、Qt5Gui.dll没有把整个 platforms 目录带上点“浏览”按钮时会直接崩溃或者什么都没发生。解决办法是发布时连同 platforms 目录一起拷贝并保证插件目录跟这些 DLL 在同级目录下。3.3 更进一步的窗口嵌入方案思路如果你不满足于弹一个独立对话框想把整个面板嵌入到 CAD 的主窗口里做成停靠面板的效果也是可行的。思路是取到 CAD 主窗口的句柄用QWindow::fromWinId封装成父窗口再把 QDialog 的 windowFlags 改成 Qt::Dialog让它挂到父窗口上。ZRX 里可以通过类似adsw_acadMainWnd()的接口拿到主窗口句柄具体函数名搜索 SDK 头文件里的 MainWnd 关键字就能找到。不过对于大多数内部工具独立模态对话框已经够用了嵌入停靠面板会引入更多窗口消息处理问题属于“锦上添花但需要投入”的部分。我建议先把弹窗方案跑通再考虑嵌不嵌的问题。4. 拿一个真实功能练手批量导出图元信息4.1 功能拆解选、取、写三步这个功能我选的是“批量导出图元摘要”操作流程是这样的用户在 CAD 里执行命令先框选一批图元然后弹出 QT 对话框选择 CSV 保存路径点“开始导出”后插件遍历选择集中的每个图元读取它的类型、图层、关键几何参数最终写进 CSV 文件。这个功能麻雀虽小五脏俱全覆盖了 ZRX 二次开发的全部核心环节交互选择、数据库打开、类型判别、属性读取、文件输出。如果你能把这个流程跑通其他任何业务功能都只是在这个骨架上换壳。4.2 选择集与图元遍历的代码骨架命令函数的第一步是让用户框选图元。这里沿用 ARX 风格的acedSSGet接口传 NULL 参数表示由用户在屏幕上交互选择static void exportInfoCommand() { ads_name ss; if (acedSSGet(NULL, NULL, NULL, NULL, ss) ! RTNORM) return; ZcDbObjectIdArray ids; ads_name ent; long len 0; acedSSLength(ss, len); for (long i 0; i len; i) { acedSSName(ss, i, ent); ZcDbObjectId id; acdbGetObjectId(id, ent); ids.append(id); } acedSSFree(ss); QString filePath; if (!showExportDialog(filePath)) return; exportEntitiesToCsv(filePath, ids); }这段代码里有个值得留意的细节我们没有在遍历选择集的同时直接打开实体而是先把对象 ID 收集到数组里等选择集释放之后再统一处理。原因很简单acedSSGet返回的选择集是一个临时句柄及时释放可以避免长时间持有选择集带来的内部锁问题。而ZcDbObjectIdArray里的 ID 是持久的后续随时可以再打开实体。4.3 几何属性读取与异常数据过滤打开实体读取属性是 ZRX 里最频繁的操作代码套路非常固定ZcDbEntity* pEnt nullptr; if (acdbOpenObject(pEnt, ids[i], ZcDb::kForRead) ! eOk) continue; QString typeName QString::fromLocal8Bit(pEnt-isA()-name()); QString layerName QString::fromLocal8Bit(pEnt-layer().getName()); if (pEnt-isKindOf(ZcDbCircle::desc())) { ZcDbCircle* pCircle ZcDbCircle::cast(pEnt); double radius pCircle-radius(); // 将半径写入输出行 } else if (pEnt-isKindOf(ZcDbLine::desc())) { ZcDbLine* pLine ZcDbLine::cast(pEnt); // 读取端点坐标 } // 其他图元类型可继续扩展 pEnt-close();这里要强调一个很多人踩过的坑用isKindOf判断类型而不是用isA()返回的类名去比较字符串。ZcDbCircle 可能有派生类你画的一个圆可能实际是某个专业软件定义的扩展圆类但它仍然是 ZcDbCircle 的派生类isKindOf能识别这种继承关系字符串比较做不到。打开实体用kForRead就够了我们只读属性不修改。如果你确实要改图元必须用kForWrite但同一时间同一实体只允许一个写打开否则整个 CAD 会进入错误状态。读完一定要 close不 close 的后果比你想的严重轻则图元被锁无法编辑重则拖累整个数据库性能。4.4 导出结果与效率体验CSV 写入我直接用了 QT 的 QFile 和 QTextStream简单直接。实际导出一个几千个图元的图纸整个过程不到一秒钟体感上就是你点完“开始导出”文件就已经躺在桌面上了。这也侧面说明 ZRX 的遍历性能没有问题真正的性能瓶颈基本都出在“每个图元都做一次类型字符串格式化”这种不必要的高频操作上这种细节在写代码时可以顺手优化掉。5. 调试、加载与发布上线5.1 开发阶段的加载和断点调试开发时每改一次代码就要编译一次然后在 CAD 里重新加载这个流程做顺了其实不慢。关键技巧是 Visual Studio 的调试器直接附加到中望CAD 的进程上。F5 启动调试时把调试命令设为中望CAD 的 exe 路径VS 会启动 CAD然后你再手动加载插件。这样插件里所有断点都能命中C 代码里各种越界、空指针问题可以直接抓到崩溃现场。5.2 发布时到底要带哪些文件这个很多人会忽略。ZRX 插件不是把生成的 .zrx 文件丢给用户就完事了如果你用了 QT 界面至少需要带上这些插件本体比如 MyPlugin.zrxQt5Core.dll、Qt5Gui.dll、Qt5Widgets.dllplatforms 目录里面有 qwindows.dllVC 运行时除非你用的运行库在目标机器上已经有了顺序和路径有讲究所有 QT 相关 DLL 必须跟 .zrx 文件放在同一个目录下同时保证 platforms 子目录也在同目录下。否则 QT 找不到平台插件最简单的文件选择对话框都弹不出来。5.3 版本兼容性的自我检查清单最后上线前我习惯过一遍这个清单用 Release 模式编译Debug 版带了一堆调试运行时目标机器不一定有目标机器上中望CAD 的位数必须匹配64 位 CAD 就上 64 位插件中望CAD 的版本年份跟 SDK 版本一致不能拿 2023 SDK 做出来的插件去 2021 上硬扛如果插件要卸载后重新加载代码里不要有全局单例残留6. 常见问题排查与避坑清单6.1 加载失败类问题加载 .zrx 时提示“无法找到程序输入点”或者直接报“模块加载失败”八成是运行时依赖没找齐。用 Process Explorer 或者 Dependency Walker 打开你的 .zrx看看缺哪个 DLL。这种问题没有技巧可言缺啥补啥补完再试。还有一个容易被忽略的如果你编译时用了 MT 静态运行库而中望CAD 内部的运行时是 MD 动态方式某些情况下会出现内存分配跨越模块边界导致崩溃。所以插件的运行库最好跟 SDK 示例工程保持一致示例用 MD 你就用 MD别自作聪明改静态链。6.2 QT运行类问题对话框能弹出来但一点“浏览”就崩溃十有八九是 platforms/qwindows.dll 没加载。解决方式是在初始化 QApplication 后主动把插件目录加入 QT 的库搜索路径QCoreApplication::addLibraryPath(QString::fromLocal8Bit(pluginDir));另一种情况是整个界面能弹但字体特别小这是高 DPI 感知问题。可以在 QApplication 构造前设置QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling)让 QT 自己处理缩放。6.3 数据操作与崩溃类问题处理图元时崩溃最常见的就两类。一类是打开实体后忘了 close尤其是循环里提前 continue 跳过了 close文件稍大必然出乱子。另一类是拿到的对象 ID 已经失效比如图元在别的地方被删了。用acdbOpenObject之前可以先用acdbIsObjectDisplayable做一次存在性检查。现象可能原因解决办法加载时提示缺失 DLLQT 或 VC 运行库没带全把依赖 DLL 和 .zrx 放同目录对话框弹不出来QApplication 未初始化或重复初始化在弹窗前调用 initQtIfNeeded点“浏览”直接崩platforms/qwindows.dll 缺失拷贝 platforms 目录并设置库路径中文变成乱码工程字符集不是 Unicode工程属性改为 Unicode处理大图时卡死实体打开后未 close检查所有分支都要 close最后再分享一个我实际使用中的体会ZRX 的开发资料虽然不如 ObjectARX 那么多但它的接口设计跟 ObjectARX 高度相似遇到不会的地方直接搜 ObjectARX 的同类问题基本都能找到思路。关键是把环境配置、QT 集成这两个地基打牢后面的业务功能都是按部就班往里填。这个项目的核心价值就在于它验证了一个结论中望CAD 二次开发用 C 加 QT虽然过程有些小坑但整体确实可以做到简单但有效且真的能用。本文还有配套的精品资源点击获取

相关新闻