简介:AutoCAD Code Pack 是为使用 AutoCAD .NET API 的 C# 开发者准备的插件开发库,面向需要定制 CAD 功能、实现自动化工作流的工程与设计领域开发者。该库封装了图形处理、图层管理、DWG 读写等常用操作,可减少重复编码,帮助开发者在创建几何形状、分析模型、定制工具栏等场景中快速落地。压缩包共 61 个文件,约 5.36MB。其中 20 个 cs 源代码文件为核心,涵盖命令注册、布局管理、数据存储等模块;13 个 dll 为编译好的引用库,可直接集成;xaml 文件用于界面定制,csproj 与 sln 构成完整工程,便于打开和调试。另有少量 XML 配置、说明文档及 license 文件,方便了解用法与授权。已有 254 人浏览学习这份资料,适合个人学习与中小型插件开发场景。开发者既可将其作为工具库直接调用,也可作为源码范例学习 AutoCAD .NET API 插件架构与常见开发模式,省去从零搭建基础代码的繁琐过程。
1. 为什么说 AutoCAD .NET API 是插件开发里的“强力工具”
第一次听到“AutoCAD插件开发库:使用AutoCAD.NET API进行开发的强力工具”这个说法,很多人第一反应是找个现成源码包直接抄。实际上,这个“插件开发库”并不是某个封装好的第三方库,而是 Autodesk 官方提供的托管接口——AutoCAD .NET API。它把 Lisp 难以处理的批量绘图、复杂 UI、与外部系统(比如 DCS、上位机)的数据交换,变成了 C# 开发者熟悉的类库调用。在工程制图和自动化集成场景里,这是一条比 VBA 和 ObjectARX C++ 更稳的落地路径,尤其适合已经有 C# 基础、不想啃 C++ 的工程师。下面按我自己的落地习惯,把版本选型、环境搭建、第一个能跑的插件、必踩的坑和进阶技巧一次讲透。
2. 把环境搭对:版本对应、要引用的 DLL 和让 AutoCAD 认出插件
2.1 版本不对一切都是玄学:ObjectARX 和 .NET Framework 的硬对应
AutoCAD .NET API 不是独立安装的,它随 ObjectARX SDK 一起提供,每个 AutoCAD 版本只保证接受对应版本的托管程序集。你需要先到 Autodesk 官网的开发者页面,下载与你的 AutoCAD 版本一致的 ObjectARX SDK。这一步不能省,用别人博客里拷贝出来的老 DLL,几乎都会在 NETLOAD 时报“无法加载程序集”或直接闪退。
版本对应关系大致是:AutoCAD 2016~2017 的目标框架多为 .NET Framework 4.5;2018~2020 一般是 4.7/4.7.2;2021 之后基本是 4.8,再新的版本要看安装包里的 Readme。这不是拍脑袋,而是因为 AutoCAD 进程本身是 .NET Framework 宿主,如果你的 C# 项目目标框架高于宿主,加载时会直接报“此程序集由更高版本的 .NET Framework 构建”;低于宿主,则可能遇到一些莫名其妙的类型行为。
| AutoCAD 版本 | 常见目标框架 | 说明 |
|---|---|---|
| 2016-2017 | .NET Framework 4.5 | 较老,需兼容旧代码 |
| 2018-2020 | .NET Framework 4.7/4.7.2 | 大多数公开例子默认值 |
| 2021 及以后 | .NET Framework 4.8 | 以官方 Readme 为准 |
Visual Studio 里新建类库后,记得把“目标框架”按上表选好,再把“平台目标”设为 AnyCPU 或 X64。AutoCAD 2020 之后基本只有 64 位版本,千万不能选 x86,也不要勾选“Prefer 32-bit”。这些设置看起来不起眼,但几乎每个新手都会在这里栽一次。
2.2 引用哪几个 DLL:AcMgd、AcDbMgd、AcCoreMgd 各管什么
解压 ObjectARX SDK 后,找到inc目录,里面有三个核心托管程序集:
AcMgd.dll:负责应用生命周期、文档集合、编辑器交互和命令行输出。AcDbMgd.dll:负责图形数据库、块表、实体读写,是开发时用得最多的程序集。AcCoreMgd.dll:提供核心运行时和部分新的 API,较新版本必须引用。
在 Visual Studio 里右键“引用 → 添加引用 → 浏览”,定位到这三个 DLL,全部选上。有的老代码还会引Autodesk.AutoCAD.Interop,那是给 COM 兼容场景用的,新项目可以先不碰。添加完引用后,在“解决方案资源管理器”里找到这三个引用,把属性中的“复制本地”(Copy Local)设为 False。如果不改,编译时会把 DLL 拷到你的输出目录,插件加载时 AutoCAD 会优先使用输出目录里的程序集,一旦和你当前 CAD 版本内置的版本不一致,就会报“无法加载文件或程序集”或触发奇怪的权限异常。
我一般还会在项目里建一个References文件夹,把三个 DLL 放进去,方便多个项目共用一份,避免每次装新 SDK 都要改引用路径。这个习惯能省很多换版本时的麻烦。
2.3 用 IExtensionApplication 给插件一个“入口”
插件类库和普通类库的区别,是要有一个主类实现IExtensionApplication。这个接口有两个方法:Initialize()在插件被加载后调用,Terminate()在卸载时调用。很多人以为必须在这里注册命令,其实[CommandMethod]标注的方法只要 DLL 被加载就能被 AutoCAD 识别,入口类主要用来注册事件、创建 UI、预加载资源。
using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.Runtime; namespace MyCadTool { public class PluginMain : IExtensionApplication { public void Initialize() { // 初始化里不要操作图形数据库,此时文档还没完全就绪 Application.DocumentManager.MDocumentActivated += OnDocActivated; } public void Terminate() { Application.DocumentManager.MDocumentActivated -= OnDocActivated; } private void OnDocActivated(object sender, DocumentCollectionEventArgs e) { // 文档激活后,可以在这里创建或更新 Ribbon、菜单 Application.MainWindow.Title = "我的CAD工具"; } } }逻辑说明:Initialize适合做一次性订阅事件,Terminate里做对称的退订。MDocumentActivated事件在文档新建、打开或切换时都会触发,是安全地访问当前文档和数据库的时机。参数说明:DocumentCollectionEventArgs中携带当前文档对象,但不要直接缓存它,因为文档可以被关闭。
2.4 三种加载方式:手动 NETLOAD、启动组和 Bundle 自动加载
调试阶段最常用的是手动加载。在 AutoCAD 命令行输入NETLOAD,弹窗选择编译出来的 DLL,命令行如果没有任何红色错误,说明加载成功。这种方式的好处是随时可以重新加载新版本,坏处是每次重开 AutoCAD 都要手动操作,不适合交付给最终用户。
第二种是把 DLL 加入启动组。输入APPLOAD,打开“加载/卸载应用程序”对话框,点击“内容”,把你的 DLL 添加进去。下次 AutoCAD 启动时会自动加载,但这种方式会锁住 DLL 文件,重新编译前必须先关闭 AutoCAD,否则 Visual Studio 会提示“无法复制文件,另一个程序正在使用”。
第三种适合分发,就是做 Bundle 自动加载。在插件目录里放一个PackageContents.xml,写明插件程序集和目标版本,用户把整个文件夹放到 CAD 的“ApplicationPlugins”目录下即可。这个方式安装体验最好,但需要额外打包逻辑,适合后期把工具交给非开发人员的场景。前两种足以覆盖日常开发和内部工具的使用。
3. 写出第一个能跑的 C# 插件:从画直线到批量导线段坐标
3.1 事务是 AutoCAD 插件的安全区:为什么所有写操作都要在 Transaction 里
AutoCAD 的图形数据库是带事务机制的,几乎所有对实体和表的读写都要包在一个事务里。很多 C# 新手会问:为什么不 new 一个 Line 然后直接加到数据库?因为数据库可能同时被其他命令访问,事务可以保证要么全部生效,要么全部回滚。最常见的做法是用using包裹Transaction,代码简洁且不会忘记提交。
using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; namespace MyCadTool { public class DrawCommands { [CommandMethod("DrawLine")] public void DrawLine() { Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord btr = (BlockTableRecord)tr.GetObject( bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); using (Line line = new Line(new Point3d(0, 0, 0), new Point3d(100, 100, 0))) { btr.AppendEntity(line); tr.AddNewlyCreatedDBObject(line, true); } tr.Commit(); } } } }逻辑说明:先打开块表,再打开模型空间表记录,然后创建直线实体并追加到表记录里。AddNewlyCreatedDBObject(line, true)告诉事务这个对象是新建的,必须在提交前登记。参数说明:BlockTableRecord.ModelSpace是模型空间在块表中的固定 ObjectId,不要自己搜表查找;OpenMode.ForWrite表示当前块表记录可写,否则追加会报错。
3.2 从 DWG 取线段:pick 一条直线并把坐标打到命令行
这个需求对应热词里常出现的“autocad dwg 获取线段 c#”。工业现场经常要把图纸里的线段数据取出来,发给测量工具或上游系统。先实现最基础的单条拾取,后续可以扩展成批量。
using Autodesk.AutoCAD.EditorInput; [CommandMethod("GetLineData")] public void GetLineData() { Document doc = Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; PromptEntityResult res = ed.GetEntity("\n选择一条直线: "); if (res.Status != PromptStatus.OK) { return; } using (Transaction tr = doc.Database.TransactionManager.StartTransaction()) { Line line = tr.GetObject(res.ObjectId, OpenMode.ForRead) as Line; if (line == null) { ed.WriteMessage("\n选中的不是直线。"); } else { ed.WriteMessage($"\n起点: {line.StartPoint}, 终点: {line.EndPoint}, 长度: {line.Length}"); } tr.Commit(); } }逻辑说明:GetEntity让用户在屏幕上拾取一个对象,返回结果里带有 ObjectId。tr.GetObject根据 ObjectId 拿到实体对象,这里用as Line做类型判断,避免用户选中多段线或圆导致强转异常。参数说明:PromptEntityResult.Status是枚举值,只有PromptStatus.OK才能继续;Editor.WriteMessage输出到命令行,比 MessageBox 更适合 CAD 环境。
3.3 批量把模型空间线段导出 CSV:上位机和 DCS 联动的前置动作
在自动化产线里,CAD 图纸里的线段坐标经常要交给上位机、DCS 或者视觉软件(比如 VisionMaster)去做路径规划。最常见的方式是把当前模型空间的所有 Line 批量导出成 CSV。这个思路能和“c# 上位机通用框架”无缝衔接:上位机只需要轮询文件或读数据库,CAD 这边做成一个命令按钮即可。
[CommandMethod("ExportLines")] public void ExportLines() { Document doc = Application.DocumentManager.MdiActiveDocument; Database db = doc.Database; var lines = new List<string>(); string fileName = @"C:\Temp\lines.csv"; using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord btr = (BlockTableRecord)tr.GetObject( bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); foreach (ObjectId id in btr) { DBObject obj = tr.GetObject(id, OpenMode.ForRead); if (obj is Line line) { lines.Add($"{line.StartPoint.X},{line.StartPoint.Y},{line.EndPoint.X},{line.EndPoint.Y}"); } } tr.Commit(); } System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(fileName)); System.IO.File.WriteAllLines(fileName, lines, System.Text.Encoding.UTF8); doc.Editor.WriteMessage($"\n共导出 {lines.Count} 条直线。"); }逻辑说明:遍历块表记录里的所有 ObjectId,用tr.GetObject逐个打开并判断类型。这里只处理模型空间,不处理块内嵌套实体,如果需要块内线段,要递归进入BlockTableRecord。参数说明:Encoding.UTF8很重要,否则 Excel 打开 CSV 时中文表头或注释会乱码;文件路径建议用可配置参数,不要硬编码在生产工具里。
3.4 为什么不能缓存 DBObject:从 access violation c0000005 到对象生命周期
热词里有“c# 调用 c++ 出现 access violation c0000005”,这在 AutoCAD .NET 开发里很常见。AutoCAD .NET API 本质是托管包装器,底层是 C++ 对象。比如你在事务里拿到一个Line对象,事务提交后这个包装器可能已经失效;如果把它存到静态字段里,下一次命令再调用属性,就会访问已释放的 C++ 对象,抛出 access violation c0000005,整个 AutoCAD 可能直接崩溃。
正确做法是只缓存ObjectId,每次需要实体时再通过tr.GetObject(id, OpenMode.ForRead)获取。ObjectId是稳定的句柄,而DBObject实例不是。这个原则也解释了为什么不能把Document或Database缓存后跨文档使用。记住:在 AutoCAD 插件里,缓存对象实例等于埋雷,缓 ID、不缓存对象。
4. 避坑与排查:1603、未知命令、DLL 被占用
4.1 AutoCAD 2020 安装失败:错误代码 1603
现象:安装 AutoCAD 2020 中途中断,日志或弹窗显示“安装失败,错误代码 1603”。有些人重装系统或卸载不干净后也会遇到。
原因:1603 是 Windows Installer 的通用失败代码,绝大多数情况是 .NET Framework 组件损坏、杀毒软件拦截或者 Visual C++ 运行库残留。AutoCAD 安装程序依赖这些基础组件,任何一环出问题都会报 1603。
解决:先彻底卸载 AutoCAD,用 Autodesk 官网的卸载工具清理注册表和安装目录;再修复 .NET Framework(去“控制面板 → 程序和功能 → 启用或关闭 Windows 功能”里重新勾选,或者用官方修复工具);最后临时关闭杀毒软件,右键安装包“以管理员身份运行”。如果还不行,看安装日志里的具体 MSI 错误码,而不是只搜 1603。
4.2 NETLOAD 能加载 DLL,但输命令提示“未知命令”
现象:命令行输入 NETLOAD 后显示加载成功,没有红色错误,但执行你的命令名时提示“未知命令”。
原因:DLL 里没有定义[CommandMethod]方法,或者方法不是 public,或者类不是 public。还有可能是命令名拼写不一致,比如你定义的是DrawLine,却输入drawline应该没问题,但如果你定义了DrawLine而输入DRAWLINE,AutoCAD 对命令不区分大小写,所以问题多半还是出在方法签名上。
解决:检查命令类和方法都声明为public,命令方法加上[CommandMethod("DrawLine")],重新编译后先卸载再加载。也可以用NETLOAD加载后,在命令行输入(vl-load-all)并用(command "DrawLine")测试,观察错误输出。如果加载时命令行出现异常堆栈,优先看第一条异常,往往就是初始化逻辑的问题。
4.3 编译时提示 DLL 被占用,或者“AutoCAD 卸载不干净无法重装”
现象:Visual Studio 生成时提示“无法复制文件,另一个程序正在使用”,重装 AutoCAD 时又提示已经有旧版本残留。
原因:AutoCAD 进程没完全退出,加载的插件 DLL 被进程锁定;另外 acwebtray、acad 等后台进程会驻留,卸载程序不一定能全部清理。
解决:编译前先保存所有图纸再关闭 AutoCAD。如果进程还残留,用任务管理器结束acad.exe和acwebtray.exe。调试项目时可以在 VS 生成事件里加一句强制结束命令,但会丢未保存图纸,慎用:
taskkill /F /IM acad.exe /T这条命令会强行关闭所有 AutoCAD 实例,只适合本地开发机。重装前还要清掉C:\Program Files\Autodesk残留目录和注册表HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk下的对应项,再用官方卸载工具扫一遍。
4.4 许可管理器不起作用或“现在将关闭 AutoCAD”
现象:NETLOAD 加载插件后,AutoCAD 弹出“许可管理器不起作用或未正确安装,现在将关闭 AutoCAD”,然后直接退出。
原因:插件在初始化阶段就触发了许可相关 API,而当前许可还没准备好;或者引用的托管程序集版本和当前 CAD 版本不一致,导致托管层调用 C++ 层时进入错误分支。
解决:不要在IExtensionApplication.Initialize里操作图形数据库,也不要在此处创建 Ribbon;把 UI 和业务初始化推迟到文档激活事件或第一次执行命令时。检查引用的AcMgd.dll/AcDbMgd.dll/AcCoreMgd.dll是否都来自当前版本 ObjectARX SDK,千万别混用。如果改过系统日期或注册表,先用/r参数重置 AutoCAD 配置再试。
4.5 新增实体后提交崩溃:AddNewlyCreatedDBObject 忘了调用
现象:代码里写了btr.AppendEntity(line),提交事务或者命令结束时 AutoCAD 崩溃。
原因:AppendEntity只是把实体放进块表记录,但事务并不知道这是一个“新创建且需要被持有”的对象。没有登记,事务提交时会尝试按既有对象处理,触发内存访问错误。
解决:在AppendEntity后立刻调用tr.AddNewlyCreatedDBObject(line, true);让事务接管新对象:
btr.AppendEntity(line); tr.AddNewlyCreatedDBObject(line, true);第二个参数传true表示这是刚由托管 API 创建的数据库对象。这个坑很隐蔽,代码越写越长越容易漏掉,建议把“创建实体”封装成公共方法,统一处理登记逻辑。
5. 把插件做成“像官方功能”:Ribbon 按钮、日志留后路和回归验证
5.1 用 Ribbon API 加一个带自定义图标的大按钮
热词里提到“net autocad中的ribbonbutton的自定义image”,这是很多工具从命令行升级到图形界面的第一步。AutoCAD 的 Ribbon 接口在Autodesk.Windows命名空间下,需要引用AcWindows.dll。下面是最小示例:
using Autodesk.Windows; public static void AddRibbonTab() { RibbonControl ribbon = ComponentManager.Ribbon; RibbonTab tab = ribbon.FindTab("MyTools"); if (tab == null) { tab = new RibbonTab { Id = "MyTools", Title = "我的工具" }; ribbon.Tabs.Add(tab); } RibbonPanelSource panel = new RibbonPanelSource { Title = "绘制与导出" }; RibbonButton btn = new RibbonButton { Text = "画线", Size = RibbonItemSize.Large, Image = LoadImageFromResource("MyCadTool.Resources.drawline.png"), CommandParameter = "DrawLine" }; panel.Items.Add(btn); tab.Panels.Add(panel); }逻辑说明:FindTab防止重复创建,Image属性接收System.Drawing.Image,建议把图标作为嵌入资源加载,不要用外部图片路径,否则部署后容易丢图。参数说明:RibbonItemSize.Large表示大按钮,CommandParameter只是携带命令名字符串,真正的点击绑定需要根据你使用的 AutoCAD 版本在按钮事件里调用SendStringToExecute或命令处理器。不同版本 Ribbon API 有差异,升级版本时要回归测试。
5.2 给插件加异常日志和回归验证:不留黑匣子
插件交付给现场后,最怕的是用户只说“刚才报错了”,你却不知道错在哪。我习惯在每个命令入口统一包一层 try/catch,并订阅AppDomain.CurrentDomain.UnhandledException,把异常时间、命令名和堆栈写到本地日志。这样至少不会变成黑匣子。
AppDomain.CurrentDomain.UnhandledException += (s, e) => { System.IO.File.AppendAllText(@"C:\Temp\cadPlugin.log", $"{System.DateTime.Now}: {(e.ExceptionObject as System.Exception)?.Message}\n"); };代码说明:这个订阅写在Initialize里,所有未捕获异常都会落到日志文件。参数说明:日志路径要保证可写,正式工具建议放在用户文档目录,不要放系统盘根目录。升级版本前,我会跑一遍“遍历当前图纸所有对象并逐个用GetObject打开”的回归命令,看是否有类型转换触发 access violation。以前我也习惯吞异常,直到现场崩溃过一次才长教训。现在宁可先慢一步,也要把日志和回归验证留足,这比多写几个命令更值。希望帮到你。
本文还有配套的精品资源,点击获取