中望CAD .NET二次开发从入门到实战:环境搭建、事务与批量操作 简介这份技术文档面向中望CAD二次开发人员系统讲解基于.NET框架支持VB.NET或C#的开发流程。文档覆盖操作环境准备包括ZWCAD 2021及以上版本、Visual Studio 2015、.NET Framework 4.7以及ZwManaged.dll、ZwDatabaseMgd.dll等核心类库的引用配置随后说明项目创建、添加引用、设置复制到本地属性等关键步骤并介绍通过NETLOAD加载编译生成的DLL、执行自定义命令的完整过程。还附有C#与VB两版示例代码演示如何在模型空间创建圆形对象并设置颜色便于开发者在实际项目中快速上手。资源体积仅307KB共1个PDF文件内容精炼但覆盖了从环境搭建到代码调试的核心环节对需要扩展中望CAD功能的工程技术人员有直接参考价值。目前已有3513人学习使用适合作为入门与日常查阅的速查手册。1. 开发环境准备版本怎么选工程怎么建1.1 中望CAD二次开发的底子.NET、ZRX与API的关系先说明一个很多人容易绕晕的点。中望CAD的二次开发并不只有.NET这一条路它还有ZRXC接口和LISP等入口。但我们这篇主要说.NET方向的开发。中望CAD的.NET API设计思路上高度兼容AutoCAD的托管API命名空间从原本的Autodesk.AutoCAD.*换成了ZwSoft.ZwCAD.*类名、方法名、调用节奏几乎是一一对应的。换句话说你如果写过AutoCAD的C#插件迁到中望CAD上很多时候就是改个命名空间、重新编译的问题。但“几乎对应”不等于“完全一样”。不同大版本里有些接口被移动过位置有些参数类型写法不同还有些功能中望这边并没有开放出来。所以一个比较稳的认知是把中望CAD的.NET开发理解成“一套和AutoCAD API长得很像但需要按自己版本来适配的独立平台”。这样你就不会一上来拿着AutoCAD的代码硬怼而是会习惯性地去查对应版本的开发文档。1.2 按目标CAD版本锁定开发环境这些年我做下来最大的坑就是版本匹配问题。中望CAD的.NET插件本质上是一个托管DLL由CAD进程在运行时加载。你的DLL编译目标是多少位的、用的哪个.NET运行时版本必须和CAD自身的运行时匹配否则加载时直接炸掉。操作上我建议按下面这个顺序来确定环境先确认你要兼容的中望CAD版本号。不同版本对应的.NET Framework版本要求不一样比如比较旧一点的版本可能最高支持.NET Framework 4.0新版可能要4.6以上甚至4.8。这一点务必打开中望CAD官方帮助文档查“系统需求”或“.NET API 开发环境”章节。按CAD版本选Visual Studio。一般用Visual Studio 2017到2022都行关键是项目目标框架要选对。我的习惯是项目属性里的“目标框架”选.NET Framework 4.7.2或4.8这两个版本在Win10/Win11上都是自带或可装的状态兼容性最稳。平台目标务必设置成x64。现在主流中望CAD都是64位进程如果你的类库编译成了AnyCPU或x86在本机调试时可能侥幸能跑换一台机器就提示“未能加载文件或程序集”排查起来非常浪费时间。注意中望CAD安装目录下一般会自带开发用的DLL比如ZwSoft.ZwCAD.dll、ZwSoft.ZwCAD.DatabaseServices.dll这些托管程序集。引用时直接浏览到安装目录去选DLL不要手动去别的地方复制。这样能保证和你本机安装的版本严格一致。1.3 创建第一个插件工程类库 命令类中望CAD的.NET插件就是一个普通的C#类库项目。它不是EXE不需要Main函数入口全靠带有特性的命令方法。步骤大致是这样新建一个“类库(.NET Framework)”项目语言选C#。项目名称随意但我建议用有意义的命名比如ZwToolkit、BatchTools之类方便后面打包和管理。引用中望CAD的托管DLL。在解决方案资源管理器里右键“引用”选“添加引用”再点“浏览”去中望CAD安装目录下把ZwSoft.ZwCAD.dll、ZwSoft.ZwCAD.DatabaseServices.dll、ZwSoft.ZwCAD.Geometry.dll、ZwSoft.ZwCAD.EditorInput.dll这几个常用程序集加进来。如果目标版本更细也可以看官方开发包里的Demo是怎么引用的照着来。把这几个DLL的“复制本地”属性设置成False。这一点很关键否则生成的目录里多出一堆几十上百兆的CAD运行时文件看着就不对劲而且容易把自己搞懵。创建一个命令类写上CommandMethod特性。这是插件注册命令的入口和AutoCAD的用法一致。using System; using ZwSoft.ZwCAD.ApplicationServices; using ZwSoft.ZwCAD.EditorInput; using ZwSoft.ZwCAD.Runtime; namespace ZwToolkit { public class Commands { [CommandMethod(HELLO_ZW)] public void HelloZw() { Document doc Application.DocumentManager.MdiActiveDocument; Editor ed doc.Editor; ed.WriteMessage(\nHello, 中望CAD .NET 插件已加载!); } } }这里HELLO_ZW就是你在CAD命令行里敲的命令名。编译成功后会生成一个DLL在CAD里用NETLOAD命令把它选中加载再输入HELLO_ZW就会在命令行看到输出。跑到这一步你的整个开发链路就算是通了。2. 先从对象模型说起不把Database和Transaction搞懂后面寸步难行2.1 “文档-数据库-块表-实体”这条主线中望CAD底层的数据组织和AutoCAD一样是个典型的四层结构Document文档里面有一个Database数据库数据库里有块表BlockTable块表里有块表记录BlockTableRecord块表记录里装着具体的图形实体比如Line、Circle、Text这些。画图的时候你屏幕上看到的东西其实都存在数据库里。而我们做二次开发绝大部分操作就是往数据库里写入实体、从数据库里读取实体、或者遍历数据库改实体属性。理解这条主线之后很多API就好猜了无非就是“通过Document拿到Database通过Database拿到BlockTable再通过BlockTable拿到ModelSpace——也就是模型空间那块块表记录然后往里面Add实体”。这就像你要往一个图书馆里放书先找到图书馆再找到对应的书架区再找到一个具体的书架然后把书摆上去。每一步都有固定的“路径”一次走错后面全乱。2.2 为什么要用事务Transaction很多刚接触的人会问我往模型空间里加一个圆直接Add不就好了为什么非要套一个事务这里涉及到CAD数据库一个很重要的设计任何对数据库内容的修改都需要放在事务里统一管理。事务可以简单理解成一个“操作记录本”。你在这个本子里写下“我要画一条直线”数据库先不真正提交等你把本子合上Commit它才一次性把所有改动写进去。如果中途任何一步出错可以整个回滚Abort数据库不会留下半截数据。这样做的好处是第一安全不会因为代码跑到一半崩溃导致图纸损坏第二性能批量修改时能减少数据库内部的频繁通知和刷新第三可恢复出错了能回到操作前状态。所以一个标准的“往模型空间加实体”的代码骨架几乎固定是这样的using (Transaction trans db.TransactionManager.StartTransaction()) { BlockTable bt trans.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; BlockTableRecord btr trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord; // 创建实体、设置属性、追加到块表记录 btr.AppendEntity(entity); trans.AddNewlyCreatedDBObject(entity, true); trans.Commit(); }注意事务一定要用using包裹或者确保最后走到Commit()或Abort()。如果买彩票一样忘了Commit数据不会出现在图纸上而且连接没释放后面操作会越来越卡。2.3 常用入口类速查开发时高频用到的几个类我整理了下类作用常用获取方式Application应用级入口拿文档管理器用静态类直接用Document当前文档能取到Editor和DatabaseApplication.DocumentManager.MdiActiveDocumentEditor命令行交互写提示、取点、取关键字doc.EditorDatabase当前图纸数据库所有实体的家doc.DatabaseBlockTable块表管理图纸里所有块定义事务中GetObject(db.BlockTableId)BlockTableRecord块表记录模型空间、图纸空间、块定义数据都在这事务中从BlockTable取Transaction事务修改数据库的统一入口db.TransactionManager.StartTransaction()这些类不需要死记但最好画一张“从Document能摸到什么”的关系图放脑子里。实际写代码的时候遇到不认识的类型猜也猜得八九不离十。3. 实战从零写一个批量加圆工具3.1 需求想清楚再动手说太多理论容易飘直接拿一个实际工具来走一遍全流程。需求描述在图纸上批量生成圆圆的数量可以由用户输入圆心位置直接鼠标在屏幕上点取每个圆半径固定为50。这个功能看起来很简单但涵盖了“命令行交互、输入参数校验、事务操作、创建实体、提交数据库”五个最基本环节。等你把这个工具理顺了后面做更复杂的功能骨架也是这套。我先把功能拆成几个动作提示用户输入要生成几个圆。提示用户指定第一个圆心位置。提示用户指定第二个圆心位置、第三个……直到数量满足。每点到一处立刻在模型空间生成一个半径为50的圆。全部结束后在命令行提示统计信息。3.2 关键代码拆开讲先看完整代码我再分段说明。[CommandMethod(BATCH_CIRCLE)] public void BatchCircle() { Document doc Application.DocumentManager.MdiActiveDocument; Database db doc.Database; Editor ed doc.Editor; // 第一步接收数量 PromptIntegerOptions countOpt new PromptIntegerOptions(\n请输入要生成的圆数量:); countOpt.AllowNone false; countOpt.LowerLimit 1; countOpt.UpperLimit 10000; PromptIntegerResult countRes ed.GetInteger(countOpt); if (countRes.Status ! PromptStatus.OK) return; int count countRes.Value; // 第二步循环取点并画圆 using (Transaction trans db.TransactionManager.StartTransaction()) { BlockTable bt trans.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; BlockTableRecord btr trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord; int added 0; for (int i 0; i count; i) { PromptPointOptions ptOpt new PromptPointOptions($\n请指定第{i 1}个圆心位置:); PromptPointResult ptRes ed.GetPoint(ptOpt); if (ptRes.Status ! PromptStatus.OK) break; using (Circle circle new Circle()) { circle.Center ptRes.Value.TransformBy(ed.CurrentUserCoordinateSystem); circle.Radius 50; btr.AppendEntity(circle); trans.AddNewlyCreatedDBObject(circle, true); } added; } trans.Commit(); ed.WriteMessage($\n已生成 {added} 个圆。); } }下面这段是重点你要理解每一句在干什么。获取交互输入。PromptIntegerOptions用来配置“请输入数字”这个交互提示。AllowNone false表示用户不能直接回车跳过LowerLimit和UpperLimit是范围约束。这里特别说一下CAD命令行交互和Web开发完全不一样用户的每一次输入都会返回一个PromptXxxResult里面有个Status字段。你不用像写表单那样抛异常去校验而是判断Status是不是PromptStatus.OK不是就直接return因为用户可能按了ESC取消。这个习惯要养成不然按ESC时程序会直接报错。创建圆实体。Circle的Center是圆心点Radius是半径。这里有个容易忽略的细节取点拿到的是UCS用户坐标系坐标而实体在数据库里存的是WCS世界坐标系坐标。如果当前图纸没动过坐标系两者一致你不会有感觉可一旦用户把坐标系旋转过直接赋值就会让圆跑偏。稳妥做法是调用TransformBy(ed.CurrentUserCoordinateSystem)把UCS坐标转换成WCS坐标。这个坑我在AutoCAD时代踩过一次后来老实用TransformBy从不出错。用using包住实体对象。你可能会好奇实体加到数据库里了为什么还要using因为如果后续代码出错了事务中途异常退出这个实体对象还没有被数据库接管不及时释放会发生对象句柄泄漏。中望CAD的API文档建议自己new出来的、还没被数据库对象完全管理的对象最好用using兜底。等Commit()之后数据库会自己接管实体生命周期你就别再手动去碰它了。3.3 加载调试的正确姿势项目编译好后调试方式有两种常见路子。一种是直接在Visual Studio里按F5把中望CAD设为启动外部程序。右键项目属性“调试”选项卡里把“启动外部程序”设为D:\Program Files\ZWSOFT\ZWCAD 2024\ZWCAD.exe。按F5时Visual Studio会启动CAD并自动附加调试器代码里下断点就能像调试普通程序一样调试插件。这种体验最好我日常开发主要用这种方式。另一种是手动在CAD里NETLOAD加载DLL再用Debug菜单的“附加到进程”选中ZWCAD.exe进程。这个方式适合排查已安装插件的运行时问题因为是手动加载部署环境更接近用户现场。调试时建议把“异常设置”里的“Common Language Runtime Exceptions”勾上。这样代码里只要扔出异常Visual Studio立刻中断能直接看到堆栈。不然异常被CAD内部吞掉你只会在命令行看到一段含糊的报错信息排查效率低得多。4. 踩坑记录加载失败、事务问题与性能优化4.1 加载失败十有八九是版本问题NETLOAD命令加载DLL报“无法加载”或“System.BadImageFormatException”时我基本不动脑子先查三件事目标平台是不是x64。前面提过CAD是64位进程DLL编译成32位或AnyCPU在运行时都可能出问题直接把平台设为x64最干净。目标框架版本是不是高于CAD实际支持的版本。比如CAD内嵌的是.NET Framework 4.6你编译成4.8加载时大概率报“方法找不到”或“架构不匹配”这类错误。解法是调低目标框架或者升级CAD版本。DLL依赖的托管程序集版本和安装目录下是否一致。如果你引用的ZwSoft.ZwCAD.dll是从A版本拷贝的但运行环境是B版本有时候能跑有时候不能跑属于典型的“本机没问题换台机就炸”。4.2 事务里最容易犯的几个错我在带新人时发现事务相关的错误是最多的而且报错往往不直观。整理几个高发的错误一读取和写入模式搞混。事务里用GetObject打开对象时第二个参数要传OpenMode.ForRead还是OpenMode.ForWrite。如果你只是读取实体坐标用ForRead如果你要修改实体属性必须用ForWrite。用ForRead打开想要改写的对象会在Write操作时抛eLockViolation异常。反过来用ForWrite打开发送只读对象虽不报错但会降低并发性能不建议养成这习惯。错误二事务嵌套不当。中望CAD支持事务嵌套但子事务的操作只有外层事务提交时才真正生效。如果你在里面开了一个事务又没提交就return数据就丢了。我的建议是单个操作尽量只开一层事务如果实在要套多层一定要理清哪个事务负责Commit、哪个负责Abort别指望外层帮你兜底。错误三忘记处理用户取消。用户随时可能按ESC你写的循环如果没判断PromptPointResult.Status下一次循环时ptRes.Value是空坐标直接创建实体就会出现无法预料的空引用异常或者画错位置。务必每次交互后立刻判断状态。4.3 大批量操作时的性能优化思路做二次开发功能正确只能算第一步大批量操作时性能才是分水岭。有次我同事写了个转图工具处理一万个实体耗时能快能慢差出20多倍最后定位到问题出在循环里频繁调用Document.Editor.WriteMessage去刷进度条。命令行输出本身不是主要瓶颈但每次输出都触发UI同步量一上去就扛不住了。几个真实有效的优化点把Document、Editor、Database提到循环外。有些代码会在循环里反复Application.DocumentManager.MdiActiveDocument这个属性每次访问都要走COM交互性能损耗不可忽略。循环开始前先取好引用循环里直接用。批量创建实体时尽量只开一个事务。事务是重量级操作每开一次都要做一堆资源分配和状态登记。批量添加一万个实体拆成一万个事务性能会惨不忍睹。正确做法是循环外开一个事务循环里追加实体循环结束统一Commit。避免循环里无意义的花哨输出。实时进度用百分比的形式很好但最好每100个或1000个输出一次而不是每一次都输出。终端刷屏本身也会拖慢速度。对象用完后该Dispose就Dispose。对新建但没加入数据库的临时实体用using包裹对从数据库GetObject拿到的对象不需要手动Dispose事务结束会自动处理。别乱调对象的Dispose()方法引用还在的事务对象被提前释放后面再访问它异常会让你怀疑人生。4.4 兼容性AutoCAD代码迁移时的自查清单如果你是从AutoCAD .NET开发转过来的我最后给一份自查清单按顺序核对完大部分代码能顺利迁移命名空间Autodesk.AutoCAD.*改成ZwSoft.ZwCAD.*这一步是全局替换但注意别误伤其他第三方库的引用。程序集引用删掉AutoCAD的dll引用添加中望CAD安装目录下对应的托管dll。CommandMethod特性命名空间不一样但用法一致确认using ZwSoft.ZwCAD.Runtime;。事务结构基本原样可跑。事件机制中望CAD的DocumentManager.DocumentCreated这些事件都有名称基本一致但触发细节可能略有差异建议调试时看事件是否被正确触发。平台差异个别中望CAD版本对某些系统API的支持不如AutoCAD完善比如高版本中望CAD支持3D实体的能力一般涉及复杂几何布尔运算时要特别测试。根据我个人经验真正让人头疼的往往不是API本身而是“你以为一样实际上有细节差异”的那部分。比如某个系统的字体映射行为、打印样式表的处理顺序这一类问题没有捷径只能在真实CAD环境里多跑多测。最后再分享一个小技巧开发时最好在电脑上同时装两个版本的中望CAD一个做主力开发一个做兼容性验证。代码在主力版本上跑通之后马上到另一个版本上重新编译加载测试一遍。不少看起来能在2023版上正常运行的功能到2024版反而因为某个接口废弃而编不过。趁早发现省得到交付的时候手忙脚乱。本文还有配套的精品资源点击获取