
简介一份使用Visual C开发的小型AutoCAD画图软件WCAD源码包适合初学MFC/GDI、或者想了解CAD软件底层实现原理的开发者参考也可用于课程设计或功能二次扩展。压缩包共172个文件以h头文件、cpp源文件、obj目标文件为主另含cur光标、bmp位图、ico图标和rc资源脚本内有完整VC工程、界面资源与可执行程序包体仅2.13MB便于快速下载和对照学习目前已有405人浏览/学习。该工程覆盖图形绘制点、直线、圆、圆弧、多边形、菜单工具栏交互、画布缩放、标注、鼠标事件、文件保存加载、性能优化与错误调试等关键模块并包含视图类、网格控制等核心源码读者可从中掌握Windows图形设备接口、MFC框架消息映射、几何计算和自定义文件格式的设计思路最终获得一个可直接编译运行并二次扩展的CAD基础项目。 做AutoCAD二次开发这些年身边总是有人问你怎么还在用VC写插件现在不是都推荐C#吗这个问题我回答过很多次。用VC也就是C配合ObjectARX这套SDK是做AutoCAD底层扩展最传统、也最彻底的一条路直到今天大量生产级插件仍然跑在这套技术栈上。正好前阵子我整理了一个小型画图软件功能不炫主要就是批量生成图框、按Excel数据自动画点位、给图元批量编号但麻雀虽小五脏俱全从环境配置、命令注册到界面交互全都涉及。这篇文章就把这个项目的完整思路和能直接抄的代码讲清楚适合那些想入AutoCAD二次开发但不知道从哪开始的人也适合正在维护旧版C插件的老哥们做个参考。1. 项目定位与技术选型为什么用VC做AutoCAD二次开发1.1 这个“小画图软件”到底在解决什么问题很多绘图工作看着简单实际重复性极高。比如一个设备分布图每次都要重新画图框、调编号、按表格数据在图上点位置一天下来眼睛都花了。我做的这个小型画图软件出发点就是把“人工重复”变成“按钮操作”用户在图面里选择一批图元工具自动算出每个图元的位置生成编号或者丢一个Excel表格进来工具逐行读数据自动把点位画到模型空间里。这种工具放在大型CAD系统里只是一小块功能但你把它独立做成一个“小型软件”就能明显体会到AutoCAD二次开发的完整链路。它不是一个脚本而是一个带界面、带数据读写、带批量处理判断的独立程序模块。整个项目涉及的知识点恰好覆盖了ObjectARX开发的核心场景环境搭建、命令注册、数据库写入、选择集遍历、MFC对话框交互以及最终的分发部署。1.2 技术路线对比ObjectARX、.NET API、AutoLISP怎么选在做技术选型之前我先把AutoCAD二次开发常见的四条路线拉出来对比了一遍。这个对比不是纸上谈兵而是结合项目实际需求来的。AutoLISP适合写小而快的脚本但碰到复杂界面和批量数据处理会非常吃力VBA在新版CAD里支持越来越弱基本不在考虑范围.NET API上手舒服文档多但底层访问能力有限自定义实体时限制很多ObjectARX用的是C性能最好能直接操作数据库底层也是做“真正的软件”最合适的选择。技术路线开发语言执行性能底层访问能力部署依赖最适合的场景AutoLISPLISP较低弱无轻量脚本、命令自动化VBAVBA低弱内嵌组件历史遗留项目.NET APIC#/VB.NET中等中等.NET Framework业务逻辑复杂的快速开发ObjectARXVC高强可自定义实体VC运行库图形底层扩展、高性能批量处理这个表格里最重要的是最后一行。我选ObjectARX还有一个现实原因这个工具要处理几千个图元的批量操作如果用AutoLISP写图面稍大就会出现明显的卡顿而用C写几千次数据库写入可以在几百毫秒内完成。另外ObjectARX可以让你自定义一个“活”的CAD实体比如你画一个带属性的图框双击它就能弹出属性框这是其他路线很难实现的。1.3 整体架构怎么拆整个项目我没有一上来就写代码而是先按模块拆成四块命令注册模块、图形生成模块、数据读写模块、界面交互模块。命令注册模块负责让CAD识别你的自定义命令图形生成模块封装了画线、画圆、生成文字、创建图框这些基础操作数据读写模块处理CSV/Excel的导入和结果导出界面交互模块用MFC做参数设置对话框和批量处理进度提示。这样拆解的好处很明显每个模块可以单独测试。比如图形生成模块封装好了我可以在命令行先手动调用测试确认画出来的图形符合要求再接上数据文件和界面最后出错时排查范围会小很多。我见过不少人写二次开发插件所有逻辑全塞在一个命令函数里看起来是写完了但只要数据格式稍变整个函数就得重写维护成本非常高。2. 开发环境与版本匹配这步踩坑率最高2.1 编译器、SDK、AutoCAD版本要“门当户对”做ObjectARX开发第一课就是版本匹配。这个坑的踩坑率极高几乎每个新手都会栽一次。ObjectARX不像C#可以跨版本跑它编译出来的arx文件对编译器版本和CAD版本要求非常严格。拿我这次用的环境为例AutoCAD 2018对应的是VS2015和ObjectARX 2018 SDKAutoCAD 2020要用VS2017AutoCAD 2021开始则是VS2019。如果编译器版本不匹配哪怕代码写得再正确加载时也会直接报“无法加载ARX文件”或“ARX命令中发生异常”之类的问题。这里我整理了一份简化版的版本对应关系方便你快速定位AutoCAD版本常用编译器目标平台2006及更早VC6.0Win322010-2012VS2008/VS2010Win32/x642014-2016VS2010/VS2012Win32/x642017-2018VS2015x642019-2020VS2017x642021-2023VS2019x64注意从AutoCAD 2013开始官方就逐步淘汰32位版本所以新项目一律按x64配置。我这次就是从VS2015 ObjectARX 2018 SDK AutoCAD 2018 x64开始做的整套环境一次跑通后面换到2020版本时也只是重新编译了一次而已。2.2 从零配置一个ObjectARX工程环境匹配只是前提工程本身的配置才是重头。我以VS2015为例把关键步骤列一下照着做基本不会错。第一步从Autodesk官网下载对应版本的ObjectARX SDK解压后目录里会有inc和lib两个文件夹。第二步在VS里新建一个Win32项目类型选DLL因为arx文件本质就是一个DLL。第三步在项目属性里把SDK的inc目录加到“附加包含目录”把lib目录加到“附加库目录”。第四步把默认入口函数删掉替换成ObjectARX的入口函数acrxEntryPoint。这里有几处特别容易踩坑。一是项目字符集建议统一使用“使用Unicode字符集”和SDK保持一致。二是运行时库Debug版用“多线程调试DLL (/MDd)”Release版用“多线程DLL (/MD)”混用会导致链接阶段报一堆莫名其妙的LNK2005错误。三是def文件如果采用传统导出方式需要确保导出了acrxEntryPoint和acrxGetApiVersion两个函数如果用SDK提供的宏也可以不写def文件但我个人还是习惯手写def排查问题时更直观。2.3 为什么VC6的老代码还能跑但新代码别再用VC6有朋友问过我既然标题里写的是“vc”那是不是要回到VC6那个年代这里要解释清楚。VC6是很多经典老项目的起点AutoCAD 2000到2006时代官方推荐的开发环境就是VC6。如果你手头有那个年代的老插件最稳妥的做法是保留一套旧环境继续维护不要轻易迁移因为涉及到的接口变化太大。但新项目我强烈不建议从VC6开始了。新版SDK已经不支持VC6编译出来的arx也无法在新版CAD里加载。我这次用的“VC”准确说就是Visual C这个技术体系的现代版本也就是VS2015。另外目标机器上要装对应版本的微软VC运行库否则arx文件加载时会因为缺少运行库而失败。这个细节很多人忘分发部署时得记得把运行库一起带上。3. 核心功能设计与实现从一条线到完整工具3.1 画一条线到底经历了什么数据库事务很多人第一次接触ObjectARX面对一堆AcDb、AcGe开头的类就懵了。这里我用一个画直线的例子来解释清楚它的底层逻辑。在ObjectARX眼里一个DWG文件就是一个数据库里面有一张块表块表里有一个模型空间记录我们画进去的每一条线、每一个圆都是这个记录里的实体。你直接记忆这个层次结构数据库 - 块表 - 块表记录 - 实体。画一条直线的代码逻辑是这样的static void drawLineCmd() { // 取当前正在编辑的数据库 AcDbDatabase* pDb acdbHostApplicationServices()-workingDatabase(); // 打开块表注意是只读打开 AcDbBlockTable* pBlockTable nullptr; pDb-getBlockTable(pBlockTable, AcDb::kForRead); // 从块表中找到模型空间记录这里需要写权限 AcDbBlockTableRecord* pRecord nullptr; pBlockTable-getAt(ACDB_MODEL_SPACE, pRecord, AcDb::kForWrite); // 创建一个直线实体并追加到模型空间 AcGePoint3d ptStart(0, 0, 0), ptEnd(100, 100, 0); AcDbLine* pLine new AcDbLine(ptStart, ptEnd); pRecord-appendAcDbEntity(pLine); // 记得关闭所有打开的对象 pLine-close(); pRecord-close(); pBlockTable-close(); }这段代码虽然短但把ObjectARX最重要的思想讲透了权限控制。打开块表用只读打开模型空间记录用写权限因为要往里塞实体创建实体的new不需要手动delete当你把它append进数据库后数据库接管了它的生命周期你只需要调用close。我见过不少新手在这里抛异常十有八九是忘记close或者权限用错了。3.2 用命令让用户操作起来像在“内置功能”代码写好了怎么让用户在CAD里调用答案是注册一个命令。ObjectARX里注册命令使用acedRegCmds-addCommand它接收四个核心参数命令组名、全局命令名、本地命令名、处理函数指针。acedRegCmds-addCommand(_T(MYTOOLS), _T(DRAWLINE), _T(DRAWLINE), ACRX_CMD_TRANSPARENT, drawLineCmd);注意命令组名的设计。组名可以把多个命令归拢在一起卸载时按组名一次性移除。这一点在做大型插件时尤其重要卸载不干净会在CAD里留下残留命令导致重复定义冲突。我在这个项目里给所有命令统一起了个组名“MYTOOLS”用户加载arx后输入DRAWLINE就能触发刚才的绘图逻辑和CAD内置命令体验完全一致。3.3 对话框交互MFC与ObjectARX怎么配合交互界面我用的是MFC对话框。项目需要设置为“在共享DLL中使用MFC”然后在工程里创建对话框资源添加控件和消息处理函数这部分跟写普通Windows程序一样。但有一个抗很典型在ARX里弹出MFC对话框时要保证对话框操作过程中CAD文档处于锁定状态。从命令函数里弹模态对话框问题不大因为命令执行期间CAD本来就锁定了文档。但如果你是在非模态对话框的按钮事件里操作数据库就一定要用acDocManager-lockDocument显式锁定当前文档否则操作数据库时会遇到eLockViolation错误程序直接崩溃。这个坑我当年在做一个自动编号工具时踩了整整一天后来才发现是锁文档的问题。3.4 数据驱动绘图Excel和CSV怎么处理小工具要实用数据读写免不了。一开始我尝试直接用VC操作Excel COM也成功读出了单元格数据但这个方案有个很尴尬的问题目标机器必须装了Excel否则COM调用直接失败。后来我改用CSV作为中间格式用C标准库fopen逐行读取既绕开了Office依赖速度也更快。这里有个细节值得说说。从CSV读到的坐标是字符串需要先atof转成double再组装成AcGePoint3d中文字段还要处理编码问题。我建议所有数据解析都封装成一个独立的CMarkDataParser类这样后续哪怕要支持Excel只需要替换解析器实现不影响上层绘图逻辑。这个项目里我还加了一个“批量写入”的优化所有实体放在同一个事务里批量提交而不是一条线一个事务。实测下来画2000个点位批量写入比逐个提交快了接近一个数量级图面也不会频繁刷新闪烁。4. 实战记录做一个批量编号标注工具4.1 先列需求清单和交互流程技术点讲得差不多了我直接拿项目里最有代表性的“批量编号标注工具”来走一遍完整流程。需求是这个用户框选一批图元工具自动算出每个图元的中心位置按照从左到右、从上到下的顺序生成编号文字并把编号结果输出到一个CSV文件里。交互流程分四步第一步用户输入命令SHOWNO第二步程序弹出MFC对话框让用户设置编号前缀和起始序号第三步用户确定后程序要求用户框选图元第四步程序自动处理并生成文字。你看这个流程看起来简单但要把界面交互、用户选择、数据库写入串起来开发过程中需要注意的细节非常多。4.2 选择集遍历的代码逻辑用户框选图元ObjectARX里用acedSSGet函数实现。它可以接收窗口选择、交叉选择、多边形选择等不同方式。代码逻辑是这样ads_name ss; // 让用户框选第一个参数为空表示允许任何选择方式 if (acedSSGet(NULL, NULL, NULL, NULL, ss) ! RTNORM) { acutPrintf(_T(\n未选中任何对象)); return; } Adesk::Int32 nLen 0; acedSSLength(ss, nLen); for (Adesk::Int32 i 0; i nLen; i) { ads_name ent; acedSSName(ss, i, ent); AcDbObjectId objId; acdbGetObjectId(objId, ent); // 打开实体并获取包围盒 AcDbEntity* pEnt nullptr; if (acdbOpenObject(pEnt, objId, AcDb::kForRead) Acad::eOk) { AcDbExtents ext; pEnt-getGeomExtents(ext); // 用包围盒中心作为编号插入点 AcGePoint3d ptCenter (ext.minPoint() ext.maxPoint()) / 2.0; // 这里生成编号并写入模型空间 pEnt-close(); } } acedSSFree(ss);这里我要提醒一点acedSSGet返回的选择集一定要用acedSSFree释放select集本质是CAD内部维护的一块内存不释放就会越积越多。另外打开实体后记得close否则批量处理几千个图元时内存占用会肉眼可见地涨上去。4.3 生成文字标注写进模型空间拿到中心点坐标后生成文字标注的代码和画直线一样也是“打开块表记录 - 创建实体 - 追加 - 关闭”这个套路。区别在于生成文字用的是AcDbMText多行文字插入点就是刚才算出来的中心点。以下是我项目里实际使用的文字生成封装AcDbObjectId addMText(const AcGePoint3d pt, const CString text, double height) { AcDbMText* pMText new AcDbMText(); pMText-setLocation(pt); pMText-setTextHeight(height); pMText-setContents(text); pMText-setAttachment(AcDbMText::kMiddleCenter); AcDbBlockTableRecord* pRecord ...; // 打开模型空间 AcDbObjectId id; pRecord-appendAcDbEntity(id, pMText); pMText-close(); pRecord-close(); return id; }编号生成顺序也有讲究。我用了最简单可靠的办法先把所有选中图元的中心点坐标读到内存里按照“Y坐标从大到小X坐标从小到大”排序这样从左到右、从上到下依次编号在大多数图纸里都符合人的阅读习惯。这个排序逻辑不复杂但很能体现实用性用户实际用下来普遍反馈编号顺序是合理的。4.4 打包与分发开发结束后面临的问题是怎么把工具交给使用的人。我的做法是编译x64 Release版本把arx文件放到一个独立目录然后附一个简单的lsp启动脚本。这样用户打开AutoCAD后只需要在命令行执行APPLOAD加载一次或者直接手动拖拽arx到CAD窗口即可。LSP脚本里可以写一个自动加载逻辑(if (not (member MYTOOLS.arx (arx))) (arxload MYTOOLS.arx) )如果把lsp文件放到CAD的启动组里就能实现CAD启动时自动加载插件对用户来说几乎是零操作。分发时我还会附带一个VC运行库安装包因为目标机器未必装了对应版本的运行库。这一步不能省很多插件装到别人电脑上不能用就是这个原因。5. 常见问题与排查笔记5.1 arx加载报版本错误或找不到入口这是群里被问得最多的问题。现象是输入APPLOAD选择arx后CAD提示无法加载。第一步检查编译器版本和CAD版本是否匹配比如VS2015编译的arx不要尝试在AutoCAD 2016上加载第二步确定是x86还是x6432位CAD只能加载32位的arx第三步检查def文件是否导出了acrxEntryPoint。我把这一项做成一个排查清单每次遇到加载问题先按顺序过一遍基本能覆盖大部分原因。5.2 LNK2005与字符集冲突链接阶段遇到LNK2005报错提示某个函数重复定义这通常是运行库混用导致的。最常见的组合是同时用了/MT和/MD或者Mix了不同版本的lib。处理办法是把项目属性的“代码生成 - 运行时库”统一设置Debug用/MDdRelease用/MD。另外如果从一个老项目里拷贝了stdafx.h但工程设置没跟上也容易出现这类问题。我的经验是先新建一个干净的工程把SDK例子跑通再往里面加自己的代码不要直接改老工程。5.3 写数据库时出现eLockViolation程序从MFC对话框的按钮消息里触发批处理时经常会遇到向数据库写入实体失败错误码是eLockViolation。原因就是当前文档没有被锁定。解决办法是在批处理函数开头加一行acDocManager-lockDocument(acDocManager-curDocument());批处理结束后再unlockDocument。但这个操作要非常小心确保unlock一定被执行否则文档一直处于锁定状态后续任何操作都会被卡住。我更推荐的做法是在对话框里只收集参数等对话框关闭后回到命令函数里再执行批处理这样CAD本身已经锁定了文档就完全不用担心锁的问题了。5.4 命令没有出现在命令行入口函数没被执行arx能加载但输入命令没反应十有八九是命令注册没执行到。排查思路是先检查acrxEntryPoint的kInitAppMsg分支里是否调用了acedRegCmds-addCommand再检查是否有编译错误导致入口switch里报错返回最后确认命令名和函数指针是否对应正确。还有一个小坑如果工程里同时有多个cpp文件入口函数只能有一个重复定义会导致链接不过。5.5 批量处理时CAD无响应处理几千个图元时界面卡住是常有的事。性能瓶颈通常在于频繁打开对象和提交事务。优化方向有三个一是能只读打开的不要用写权限二是能批量提交的不要分多次提交三是处理过程中可以调用acdbHostApplicationServices()-workingDatabase()-disableUndoRecording(true)临时关闭撤销记录。这个函数不是特别常用但处理大数据量时效果非常明显处理完记得恢复否则用户按CtrlZ会失效。6. 一点维护老项目的体会项目做完到现在我自己最大的感受是ObjectARX这套体系虽然门槛高但它给你的是底层控制力一旦摸熟了后面做任何自动化工具都顺手。比起脚本它是一种“做软件”的思路有清晰的工程结构、完整的错误处理、可控的性能表现。如果你也想拿VC做AutoCAD二次开发我建议从一个极小的功能开始比如就给当前图层加一条带属性的线先把环境跑通再一步步往上加功能。版本匹配表、SDK文档、以及加载错误的排查方法都比“报错后懵住”更有用。这套路走通之后你会发现“用VC开发小型AutoCAD画图软件”这件事门槛比你想象的低得多只是需要耐心跨过开头几个坑而已。本文还有配套的精品资源点击获取