简介:这份资源面向AutoCAD二次开发人员与.NET工程师,聚焦不依赖AutoCAD环境直接读写DWG文件这一常见难题。Teigha(原名OpenDwg、DWGdirect)相关开发资料在网络上极为稀缺,作者花费大量精力收集整理,并给出VB.NET与C#两套可直接参考的源码示例,帮助读者快速理解DWG文件解析与写入的核心流程。压缩包共165个文件,约61.5MB,包含62个dll类库、6个vb与6个cs源码文件、6个exe示例程序,以及config、manifest、resx、sln、csproj、vbproj等工程配置与资源文件,另附DWGdirect_NET_3_02、Teigha_Net_40010及配套帮助文档,结构完整便于直接编译调试。目前已有3826人学习下载,适合需要脱离AutoCAD批量处理图纸、做格式转换或搭建轻量级DWG读写工具的中高级开发者参考。
1. Teigha4 读写 DWG:为什么老工程师还在用它
手上接过一个老厂区的竣工图,甲方要求把 DWG 里的图层、块参照、扩展数据全部抽出来,再按规则回写一批标注。试过在线转换、试过 ODA 官方新版本,最后能稳定跑通、还能在 VB.NET 老项目里直接引用的,还是 Teigha4 那一套。Teigha4 是 ODA 早期发布的一套 DWG/DXF 读写组件,配套的 DWGdirect_NET_3_02 和 Teigha_Net_40010 是当年 .NET 平台接入 DWG 的主流方式,C# 和 VB.NET 都能直接调。它解决的核心问题很具体:不依赖 AutoCAD 客户端,纯托管代码打开 DWG,读实体、改属性、另存回 DWG。适合谁?做土木、机械、GIS 数据转换的 .NET 开发者,尤其是维护存量项目、需要离线批量处理图纸的人。这一章先把「它是什么、能干什么」讲清楚,后面几章落到代码和参数。
2. Teigha4 与 DWGdirect_NET 的选型账:版本、依赖与运行环境
2.1 三个组件到底谁管什么
标题里出现了三个名字,很多人第一次接触会混。Teigha4 是 ODA 的 SDK 代号,底层是 C++ 的 DWG 读写引擎;DWGdirect_NET_3_02 是这套引擎在 .NET 上的早期封装,命名空间以Teigha.DatabaseServices为主;Teigha_Net_40010 是后续的 .NET 程序集版本,接口基本延续,但部分类名和加载方式有调整。三者关系可以这样理解:Teigha4 是引擎,DWGdirect_NET 和 Teigha_Net 是不同时期给 .NET 用的「外壳」。
选型时先确认一件事:你的项目是 .NET Framework 还是 .NET Core。DWGdirect_NET_3_02 和 Teigha_Net_40010 都是面向 .NET Framework 的,典型是 4.x。如果你的项目已经迁到 .NET 6/8,直接引用会报BadImageFormatException或找不到依赖,这时候要么把读写逻辑拆成一个独立的 Framework 控制台程序,要么换 ODA 更新的 .NET 包。我一般建议:存量项目继续用 DWGdirect_NET_3_02,新项目如果非要用 Teigha 系,优先试 Teigha_Net_40010,接口更整齐。
2.2 部署时必须带上的原生依赖
纯托管程序集只是壳,真正干活的是同目录下的原生 DLL。以 DWGdirect_NET_3_02 为例,bin目录里除了TD_Mgd_3.02_10.dll这类托管程序集,还会有TD_Db_3.02_10.dll、TD_Ge_3.02_10.dll、TD_Root_3.02_10.dll等原生库。少一个,运行时就抛DllNotFoundException。
部署清单可以按这个表核对:
| 文件类型 | 示例 | 作用 | 缺失后果 |
|---|---|---|---|
| 托管程序集 | TD_Mgd_3.02_10.dll | .NET 调用入口 | 编译期找不到类型 |
| 原生数据库库 | TD_Db_3.02_10.dll | DWG 实体读写 | 打开图纸即崩 |
| 原生几何库 | TD_Ge_3.02_10.dll | 点、向量、矩阵 | 读取坐标报错 |
| 根库 | TD_Root_3.02_10.dll | 内存与基础服务 | 初始化失败 |
提示:这些 DLL 必须和你的 exe 在同一目录,或者放在系统 PATH 能找到的目录。放到子目录再用
SetDllDirectory也行,但老项目里最常见的问题就是「编译过了,一跑就崩」,九成是原生库没拷全。
2.3 初始化与许可:别跳过的那几行
Teigha 系组件在真正打开数据库前,需要做一次宿主初始化。不同版本写法略有差异,DWGdirect_NET_3_02 常见做法是调用Teigha.Runtime.Services的Initialize,并传入宿主应用名。下面这段是 C# 的最小初始化骨架:
using Teigha.Runtime; using Teigha.DatabaseServices; // 宿主应用名随便给,但不要为空 HostApplicationServices.HostVersion = "1.0"; // 初始化 Teigha 运行时,只需一次 Services.Initialize("MyDwgApp"); // 程序退出前记得反初始化 // Services.Uninitialize();逻辑说明:Services.Initialize会加载原生库并建立内部服务表,不调用它直接new Database()会抛异常。参数是宿主应用标识,用于日志和许可校验,不是路径。Uninitialize在进程退出前调用,避免原生资源泄漏。VB.NET 写法完全对应:
Imports Teigha.Runtime Imports Teigha.DatabaseServices HostApplicationServices.HostVersion = "1.0" Services.Initialize("MyDwgApp")参数说明:HostVersion是字符串,填你的应用版本即可,不影响读写结果。初始化只需在程序启动时做一次,不要每次打开图纸都调。
3. 用 C# 和 VB.NET 读写 DWG 的最小可运行路径
3.1 打开 DWG 并遍历模型空间实体
读 DWG 的核心对象是Database,模型空间通过BlockTable找到ModelSpace块表记录,再遍历其中的实体。下面这段 C# 打开一个 DWG,打印所有实体的类型和句柄:
using (Database db = new Database(false, true)) { // 第二个参数 true 表示允许打开未保存的图纸 db.ReadDwgFile(@"D:\test.dwg", FileOpenMode.OpenForReadAndAllShare, false, null); using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); foreach (ObjectId id in ms) { Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity; if (ent == null) continue; // Handle 是 DWG 内部唯一标识,调试时很有用 Console.WriteLine($"{ent.GetType().Name} handle={ent.Handle}"); } tr.Commit(); } }逻辑说明:ReadDwgFile把磁盘文件读进内存数据库,StartTransaction开启事务,所有读取都要在事务里做。BlockTableRecord.ModelSpace是模型空间固定入口,布局空间要用PaperSpace。ent.Handle是十六进制字符串,回写时用它定位实体最稳。
参数说明:ReadDwgFile第一个参数是路径,第二个FileOpenMode常用OpenForReadAndAllShare,允许多进程同时读;第三个bool是「允许打开未保存」,一般 false;第四个是密码,无密码传 null。VB.NET 对应写法:
Using db As New Database(False, True) db.ReadDwgFile("D:\test.dwg", FileOpenMode.OpenForReadAndAllShare, False, Nothing) Using tr As Transaction = db.TransactionManager.StartTransaction() Dim bt As BlockTable = tr.GetObject(db.BlockTableId, OpenMode.ForRead) Dim ms As BlockTableRecord = tr.GetObject(bt(BlockTableRecord.ModelSpace), OpenMode.ForRead) For Each id As ObjectId In ms Dim ent As Entity = TryCast(tr.GetObject(id, OpenMode.ForRead), Entity) If ent IsNot Nothing Then Console.WriteLine(ent.GetType().Name & " handle=" & ent.Handle) End If Next tr.Commit() End Using End Using3.2 修改实体属性并另存回 DWG
读只是第一步,真正落地往往是改。比如把所有圆半径放大 1.1 倍,再另存为新文件:
using (Database db = new Database(false, true)) { db.ReadDwgFile(@"D:\in.dwg", FileOpenMode.OpenForReadAndAllShare, false, null); using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); foreach (ObjectId id in ms) { Circle c = tr.GetObject(id, OpenMode.ForWrite) as Circle; if (c == null) continue; c.Radius *= 1.1; // 半径放大 } tr.Commit(); } // 另存为,不要覆盖原文件 db.SaveAs(@"D:\out.dwg", DwgVersion.Current); }逻辑说明:写操作必须用OpenMode.ForWrite打开对象,否则抛eNotOpenForWrite。SaveAs的第二个参数是 DWG 版本,DwgVersion.Current表示当前库支持的版本,也可以指定AC1015(2000)、AC1018(2004)等。参数说明:SaveAs第一个参数是输出路径,路径目录必须存在,否则抛异常。
注意:不要对同一个
Database既ReadDwgFile又SaveAs到原路径,容易在部分版本上出现文件锁冲突。稳妥做法是读原文件、写到新文件,再在业务层替换。
3.3 块参照与扩展数据的读取
DWG 里大量信息藏在块参照和 XData 里。读块参照要先拿BlockReference,再通过BlockTableRecord找块定义;XData 用GetXDataForApplication。下面这段读指定 AppName 的扩展数据:
using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); foreach (ObjectId id in ms) { Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity; if (ent == null) continue; // 读取注册在 "MYAPP" 下的扩展数据 ResultBuffer rb = ent.GetXDataForApplication("MYAPP"); if (rb == null) continue; foreach (TypedValue tv in rb) { Console.WriteLine($"type={tv.TypeCode} value={tv.Value}"); } } tr.Commit(); }逻辑说明:GetXDataForApplication返回ResultBuffer,里面是TypedValue列表,TypeCode表示数据类型(1000 字符串、1040 实数、1070 短整型等)。参数说明:AppName 必须和写入时注册的一致,大小写敏感。VB.NET 里For Each tv As TypedValue In rb即可。
4. 避坑与排查:Teigha4 读写 DWG 最常见的 5 个翻车点
4.1 现象:一打开图纸就抛 DllNotFoundException
原因:原生 DLL 没拷到输出目录,或者位数不匹配(x64 程序引了 x86 库)。解决:确认托管程序集和原生库位数一致,把TD_*.dll全部拷到 exe 同目录;用dumpbin /headers看目标平台,或在项目属性里锁定x64。
4.2 现象:读到的实体数量比 AutoCAD 里少
原因:只遍历了模型空间,没处理布局空间和块定义内部实体;或者图纸里有代理实体(Proxy Entity),Teigha 默认不展开。解决:同时遍历BlockTableRecord.PaperSpace;对块参照用BlockReference.BlockTableRecord递归进去;代理实体需要额外处理,读不到就跳过并记录。
4.3 现象:SaveAs 后文件打不开或提示损坏
原因:写操作没提交事务就 SaveAs,或者对只读打开的对象做了修改。解决:确保所有写操作在tr.Commit()之后再做SaveAs;写对象必须OpenMode.ForWrite;输出路径不要和输入相同。
4.4 现象:中文图层名或文字乱码
原因:DWG 内部字符串编码和 .NET 默认编码不一致,老版本尤其明显。解决:读取时用TypedValue的原始值,不要过早ToString();写回时确认Database的DwgCodepage设置,必要时显式指定代码页。
4.5 现象:批量处理几百张图后内存暴涨
原因:Database和Transaction没释放,原生内存不归 GC 管。解决:每个文件用using包住Database,事务及时Commit或Abort;处理完一批后手动GC.Collect()并WaitForPendingFinalizers(),虽然不优雅,但对付原生库有效。
5. 进阶:用 Handle 做增量回写与版本兼容验证
真正在生产里跑,最怕的是「全量重写」把别人改过的图覆盖掉。我后来固定用一个习惯:读的时候把每个实体的Handle和关键属性记下来,回写时只按Handle定位并改需要改的字段,其余原样保留。下面这段演示按 Handle 找实体并改文字内容:
// handleStr 来自上一次读取时记录的 ent.Handle.ToString() Handle h = new Handle(Convert.ToInt64(handleStr, 16)); ObjectId id = db.GetObjectId(false, h, 0); using (Transaction tr = db.TransactionManager.StartTransaction()) { DBText txt = tr.GetObject(id, OpenMode.ForWrite) as DBText; if (txt != null) { txt.TextString = "已复核"; // 只改内容,不动位置和样式 } tr.Commit(); }逻辑说明:Handle是 DWG 内部持久标识,跨会话稳定;GetObjectId用Handle反查ObjectId,第三个参数 0 表示不限制。参数说明:Convert.ToInt64(handleStr, 16)把十六进制字符串转长整型,注意Handle可能超过int范围,必须用long。
版本兼容验证我一般做三步:先用 Teigha_Net_40010 读一遍,再用 DWGdirect_NET_3_02 读同一张图,对比实体数量和关键属性;然后写回一个副本,用 AutoCAD 或 ODA 查看器打开确认无损坏;最后把副本再读一次,确认回写内容可被再次解析。这套流程跑下来,基本能挡住「读得到、写坏了」这类最坑的问题。
踩过最疼的一次,是没做 Handle 校验就直接全量 SaveAs,结果把同事加的一批标注冲掉了,返工了一下午。从那以后,凡是回写,先备份、再按 Handle 增量改、最后做一次回读验证,成了我的固定动作。希望帮到你。
本文还有配套的精品资源,点击获取