
简介面向AutoCAD二次开发人员、CAD插件开发者以及需要批量处理DWG图纸的技术人员这套资源围绕Teigha早期曾用名OpenDWG、DWGdirect整理了一套完整的DWG文件读写实现方案重点解决不启动AutoCAD也能直接读取图元、图层、块表、线型、标注等图纸数据的问题。压缩包共165个文件整体大小约61.5MB其中包含62个Teigha相关DLL核心库、33个tx数据文件以及VB.NET与C#两套原生工程源码含Teigha读取Dwg工程和RepText工程附带若干exe演示程序、config配置文件和DWGdirect_NET_3_02、Teigha_Net_40010帮助文档方便开发者查阅API方法、版本差异和依赖关系。已有3826人浏览学习。资源中除了可直接运行的示例工程外还整理了Teigha运行环境搭建、常见引用错误、文件句柄释放和批量转换等排错要点覆盖从入门到实战的关键路径能帮助开发者在处理DWG读取与转换时少走弯路快速产出可用工具。1. 脱离 AutoCAD 读写 DWGTeigha 这套库到底解决什么问题做 DWG 读写很多项目一开始的思路都是装 AutoCAD再用 COM 或 .NET API 操作它。可真要做服务端批处理、给 Web 系统提供图形解析、做 dwg 转 shp 这类数据交换时AutoCAD 这种重量级依赖就成了最大的坑服务器要买授权、要常驻进程、还要处理崩溃恢复。Teigha后来统一叫 ODA Platform就是专门绕开 AutoCAD 的 SDK标题里的 DWGdirect_NET_3_02 和 Teigha_Net_40010 是两代 .NET 封装配合官方帮助和 vb.net、C# 示例可以直接在普通 Windows 服务里读 DWG、写 DWG。这篇文章把初始化、遍历、新建、保存和最容易翻车的地方按可复现的顺序讲清楚适合要做图纸数据提取、格式转换和在线预览后端解析的 .NET 开发者。2. 选型先于动手DWGdirect_NET_3_02 与 Teigha_Net_40010 的定位差别和初始化写法2.1 两代库的产品线关系别把它们当成同一套东西DWGdirect 是 ODA 在 Teigha 品牌之前的产品名DWGdirect_NET_3_02 是 3.0.2 时代的老封装Teigha_Net_40010 是 4.0.10 的新内核封装。从命名空间到实体支持范围两者都有代差。标题把它们放在一起不是为了让你同时引用而是让你在迁移老工程时知道手里有两个选择老图纸、老系统用 DWGdirect_NET_3_02 跑新开发直接上手 Teigha_Net_40010。我一般会这么判断选型如果项目要长期维护、要支持 2010 以上的 DWG 格式就直接走 Teigha_Net_40010只有遇到旧格式兼容性或者要对照老代码行为时才单独开一个工程跑 DWGdirect。两代库的托管程序集名接近硬塞进同一个项目后面讲坑的时候会说到那是典型的自找麻烦。官方帮助里最值得先翻的不是安装说明而是 Object Model Reference——它按命名空间列出所有类Database、Transaction、BlockTableRecord 的继承关系一目了然这比到处找示例代码靠谱得多。2.2 C# 初始化 Teigha 运行时的最小代码Teigha .NET 和 AutoCAD .NET API 的形态很接近但入口不一样。AutoCAD 二次开发是插件形式运行环境由 AutoCAD 提供Teigha 是独立进程必须自己先注册一个 HostAppServices 实例让原生内核知道当前进程是谁。using System; using Teigha.Core; using Teigha.Runtime; using Teigha.DatabaseServices; namespace DwgLab { public sealed class DemoHost : HostAppServices { public override string getLocale() zh-CN; } public static class Program { // 必须持有引用防止 host 被 GC 回收 private static DemoHost _host; public static void Main() { _host new DemoHost(); // 到这里 Teigha 运行时才算真正就绪 Console.WriteLine(Teigha runtime initialized.); } } }这段代码里三个命名空间各有分工Teigha.Core 放的是内核基础类型Teigha.Runtime 里是 HostAppServices 和异常类型Teigha.DatabaseServices 才是 Database、Transaction、实体类所在的地方。getLocale 返回语言标识影响错误消息和默认文本的生成。注意 _host 必须存成静态字段很多人第一步就死在直接 new 完不存引用程序一碰 Native 层就崩而且崩得没有规律。2.3 vb.net 的等价写法与引用配置清单vb.net 的初始化跟 C# 完全对应只是语法不同。实际操作里我见过不少人是把 C# 示例硬翻译成 vb.net其实只要继承同一个基类、按同样的顺序调用行为完全一致。Imports Teigha.Runtime Imports Teigha.DatabaseServices Public Class DemoHost Inherits HostAppServices Public Overrides Function getLocale() As String Return zh-CN End Function End Class Module Program Private _host As DemoHost Sub Main() _host New DemoHost() Console.WriteLine(Teigha runtime initialized.) End Sub End Module无论哪种语言引用配置上绕不开三条托管程序集要复制到输出目录原生 TD_*.dll 必须和主程序在同一目录进程位数必须和原生库一致。常见的 BadImageFormatException 就是这么来的——项目默认 AnyCPU在 64 位系统上跑 32 位原生库或者反过来。Teigha 4.0.10 那一代基本按 .NET Framework 4.x 构建我建议目标框架直接用 .NET Framework 4.7.2别急着上 .NET 6/8程序集加载问题会少很多。3. 读取 DWG 并提取几何信息从黑匣子里拿到坐标、图层和文字3.1 打开模型空间遍历实体的最小流程Teigha 的读取模型和 AutoCAD .NET 几乎一致先建 Database再开事务从 BlockTable 找到模型空间 BlockTableRecord遍历里面的 ObjectId。DWG 文件格式本身是个黑匣子但这套 API 把里面的块表、层表、实体都映射成了托管对象。using (Database db new Database(false)) { db.ReadDwgFile(C:\test\drawing.dwg, System.IO.FileShare.Read, true, ); db.CloseInput(true); using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTable bt (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord model (BlockTableRecord)tr.GetObject( bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); foreach (ObjectId id in model) { DBObject obj tr.GetObject(id, OpenMode.ForRead); // 实体分发逻辑见 3.2 Console.WriteLine(obj.GetType().Name); } tr.Commit(); } }Database(false) 是创建一个不带默认对象模型的空库再接 ReadDwgFile 把文件灌进来如果直接 new Database(true, true)会连带生成默认的层表、线型表和模型空间那是为新建图纸准备的。ReadDwgFile 的第三个参数 bAllowCP56 一般传 true表示允许某些非标准字符集兼容处理遇到中文乱码时这个开关很关键。CloseInput(true) 必须在读取结束后调用否则文件句柄不释放后面想改文件名或删文件都会失败。3.2 实体分发Line、Circle、DBText、Polyline 的取值代码遍历到每个实体后真正要干的是按类型取数据。读取 DWG 的目的大多不是显示而是把几何信息抽出来给下游用比如 dwg 转 shp 的第一步就是把实体坐标完整拿全dwg 在线预览的后端解析也走同一条路。switch (obj) { case Line line: var start line.StartPoint; var end line.EndPoint; break; case Circle cir: var center cir.Center; double radius cir.Radius; break; case DBText txt: string content txt.TextString; string layer txt.Layer; break; case Polyline pl: for (int i 0; i pl.NumberOfVertices; i) { var pt pl.GetPoint2dAt(i); // 注意这里是二维点Z 需要从 Elevation 属性拿 } break; }Line 的 StartPoint、EndPoint 是 Point3dCircle 的 Center 也是 Point3d这些类型在 Teigha.Geometry 命名空间里记得补 using。DBText 的 TextString 是显示字符串如果图里有追加的格式代码比如上下标它会包含在内但多数业务场景拿这个就够。Polyline 是二维多段线GetPoint2dAt(i) 拿到的是平面坐标高层要另外读 Elevation 属性如果是三维多段线实体类型是 Polyline3d遍历方式完全不同分发时别漏了 ObjectClass 判断。每个实体都要在这个 switch 里把需要的数据拷贝到自己的结构里不要只存实体引用事务一结束这些对象就不可用了。3.3 事务与对象生命周期为什么不能跨事务存引用Teigha 的托管对象不是普通 .NET 对象它背后包着原生内核的句柄。事务结束、对象被 Dispose 之后再访问属性会抛 ObjectDisposedException 或者干脆崩进程。这个问题在遍历大图时特别明显foreach 里拿到 obj如果想着先存进 List等事务提交后再慢慢读那基本是定时炸弹。正确姿势是在事务内把需要的值全部拷出来存成普通的数据结构比如坐标数组、字典、自定义 DTO。另一个容易踩的点是 foreach 里主动调用 obj.Dispose()然后下一次循环又用 tr.GetObject 去拿同一个 ObjectId这会导致对象被提前释放后续循环里 Teigha 抛“对象已销毁”之类的异常。我的习惯是对象生命周期完全交给事务管理不在循环体内手动 Dispose需要释放引用时统一在事务结束后处理。4. 写入 DWG 和另存新建数据库、批量改图层、合并图纸4.1 从零建一张 DWG 并写入一条 Line 的完整流程读是基础写才是很多业务真正卡住的地方。新建图纸和读文件的思路刚好相反先建带默认设置的数据库再开事务往模型空间追加实体最后 SaveAs 指定版本。这里有一个我见过无数人翻车的顺序问题AppendEntity 和 AddNewlyCreatedDBObject 必须成对出现而且 AppendEntity 在前。using (Database db new Database(true, true)) { using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTable bt (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForWrite); BlockTableRecord model (BlockTableRecord)tr.GetObject( bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); Line line new Line(); line.StartPoint new Point3d(0, 0, 0); line.EndPoint new Point3d(100, 80, 0); line.Layer 0; model.AppendEntity(line); tr.AddNewlyCreatedDBObject(line, true); tr.Commit(); } db.SaveAs(C:\out\result.dwg, DwgVersion.AC1027); }Database(true, true) 的第二个参数 noTransaction 传 true表示建库时不自动开启顶层事务给后面手动 StartTransaction 让路。Line 创建后只是托管对象必须先 AppendEntity 注册到块表记录再把新建对象告诉事务否则它根本不进数据库。保存用的 DwgVersion 枚举跟 AutoCad 版本一一对应AC1027 是 AutoCAD 2013 格式Teigha 4.0.10 这一代最高覆盖到 2013写 AC10322018 格式会直接报不支持。4.2 读取-修改-写回批量改图层和替换文字的套路实际业务里最常用的是把现有图纸读进来批量改点东西再另存出去。比如把某几层上的线全改成另一层或者把所有旧图纸号替换成新图纸号。这比从零建图简单因为对象模型已经存在不需要关心默认设置。db.ReadDwgFile(inputPath, System.IO.FileShare.Read, true, ); db.CloseInput(true); using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTable bt (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord model (BlockTableRecord)tr.GetObject( bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); foreach (ObjectId id in model) { DBObject obj tr.GetObject(id, OpenMode.ForRead); if (obj is Line line line.Layer OLD-LAYER) { obj.UpgradeOpen(); line.Layer NEW-LAYER; } } tr.Commit(); } db.SaveAs(outputPath, DwgVersion.AC1027);事务里默认以 ForRead 方式打开对象要改属性必须先 UpgradeOpen() 把读锁升级成写锁。这个操作会在实体上短暂加排他锁批量循环里如果遇到同一个对象被多次打开Teigha 会报对象正在使用。我的习惯是尽量在循环内只处理一次避免同一个 ObjectId 打开两次。另外改完不调 SaveAs 的话所有修改只停留在内存里事务 Commit 只是把修改标记为有效不会自动落盘。4.3 多张 DWG 合并实体克隆与块表定义的正确处理“c# dwg 合并”是我在开发里看到被问得最多的需求。把 A 图纸的模型空间实体搬进 B 图纸最直接的做法是遍历源模型空间逐个 Clone 实体到目标数据库。但这里有个隐藏很深的坑如果实体是 Insert块引用它引用的块表记录和嵌套实体不会自动跟着走Clone 出来的可能是个残缺引用。图面上看着不报错一打开就是块内容丢失。// srcDb 和 targetDb 都打开后在各自事务里操作 foreach (ObjectId id in srcModel) { // 先过滤只克隆基础实体 if (id.ObjectClass.DxfName LINE || id.ObjectClass.DxfName CIRCLE || id.ObjectClass.DxfName TEXT) { DBObject srcObj trSrc.GetObject(id, OpenMode.ForRead); DBObject cloned srcObj.Clone(); targetModel.AppendEntity((Entity)cloned); trTarget.AddNewlyCreatedDBObject(cloned, true); } }这段代码对纯几何实体够用但一碰到 Insert、尺寸标注、带属性的块就会漏东西。我的经验是如果图纸里块引用很多就不要用逐实体 Clone而是用 Wblock 思路把源图整个块表记录复制过去再做对象映射。Teigha 的 ObjectModel Reference 里能查到 WblockCloneObjects 相关方法那是按一组 ObjectId 批量克隆并自动处理依赖关系的正路。逐实体 Clone 只适合格式干净的图纸生产环境里的图纸永远不干净。4.4 保存版本的参数矩阵与降版本损失SaveAs 时选错版本最常见的后果不是报错而是开图时被新版 AutoCAD 提示“图形文件版本不兼容”。版本参数不是随便填的这里给出一张直接用得上的对照表DwgVersion 枚举AutoCAD 版本说明AC10152000兼容性最好但会丢现代特性AC10182004老版本软件常用AC10212007支持较多实体类型AC10242010常见交换格式AC10272013Teigha 4.0.10 的可靠上限降版本不是简单换个枚举值某些实体在旧格式里没有对应表达会被替换成代理实体或者干脆丢失。比如高版本里的注释性缩放、动态块降级后可能只剩静态表现。我的做法是保存前先用一个只读事务统计实体类型如果包含新特性实体就提醒业务方这个文件降版本会有视觉损失能不能接受得由需求方拍板。5. Teigha 踩坑排查初始化、版本、内存和引用冲突的血泪经验5.1 程序一启动就闪退或者初始化静默失败现象Main 函数里 new 完 HostAppServices还没碰任何 DWG 文件程序就退出或者调用 ReadDwgFile 时直接 crash连异常都不给。原因大概率是 host 对象没被静态持有被 GC 回收了也可能是原生 TD_*.dll 没在输出目录。解决把 host 存成静态字段检查项目的输出目录原生库和托管库必须都在再核对进程位数和原生库位数一致。5.2 打开高版本 DWG 报“file was created by a newer version”现象网上下载的样例图能开甲方发来的新图一读就抛版本异常。原因Teigha_Net_40010 是 4.0.10 内核最高支持到 AC1027AutoCAD 20132018 格式打开不了。解决要么让甲方在 AutoCAD 里另存为 2013 或更低版本要么升级到支持 AC1032 的新版 ODA Platform。遇到这种情况不要怀疑代码写错了先确认文件版本。5.3 大图读取时内存暴涨长循环里偶发 ObjectDisposedException现象一两百 MB 的 DWG 跑完遍历内存峰值翻了数倍且报错点位随机。原因事务里 GetObject 返回的每个对象都在原生层占资源循环里创建了大量临时对象又没有及时释放更隐蔽的是你把实体引用存进了 List事务结束后才去访问。解决循环体里只保留 ObjectId需要的数据立刻拷贝成普通类型如果确实要缓冲实体用 DictionaryObjectId, 自定义结构别存 DBObject 引用。5.4 同时引用 DWGdirect 和 Teigha编译报命名空间冲突现象把 DWGdirect_NET_3_02 的 DLL 和 Teigha_Net_40010 的 DLL 都加进引用代码里 using Teigha.DatabaseServices编译却提示类型不明确。原因两代库的同名命名空间、同名类很多程序集又没有被强签名隔离。解决一个项目只用一套库分开两个工程编译再通过进程间通信或者中间数据文件对接。标题里两个资源包是历史资产不是让你混用的我吃过这个亏最后老实拆工程。5.5 服务器上没有字体和 SHX 文件文字读取现象异常现象读取包含中文或特殊字体的图纸DBText 的 TextString 拿到的内容看起来对但图形预览时文字全是方块有些实体直接变成 Proxy 对象。原因服务器缺少 SHX 或系统字体Teigha 无法完成字体替换但文本数据和几何数据仍然保留。解决解析数据时只依赖 TextString 和 StyleName别依赖渲染需要渲染时在环境中提供字体替代配置。这条不是 bug是环境问题想明白边界就不慌。6. 把读写封装成可回归验证的工具类前面的代码都是直写流程工程里调用多了之后我习惯封装成一个静态工具类把打开、遍历、取值、关闭固定成一条链路。这样业务层不用每次关心事务和生命周期。public static class DwgReader { public static ListDictionarystring, object ReadEntities(string path) { var result new ListDictionarystring, object(); using (var db new Database(false)) { db.ReadDwgFile(path, System.IO.FileShare.Read, true, ); db.CloseInput(true); using (var tr db.TransactionManager.StartTransaction()) { var bt (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); var model (BlockTableRecord)tr.GetObject( bt[BlockTableRecord.ModelSpace], OpenMode.ForRead); foreach (ObjectId id in model) { var obj tr.GetObject(id, OpenMode.ForRead); var item new Dictionarystring, object(); // 按 DxfName 分发避免 switch 越滚越长 switch (obj.GetType().Name) { case Line: var line (Line)obj; item[type] LINE; item[start] line.StartPoint; item[end] line.EndPoint; break; case DBText: var txt (DBText)obj; item[type] TEXT; item[text] txt.TextString; break; } if (item.Count 0) result.Add(item); } tr.Commit(); } } return result; } }封装的验证方法我一般这么干准备一个小型回归目录按 DWG 版本分桶放样例图跑完批处理之后统计每个文件的实体类型和数量再挑几张图用 AutoCAD 打开对比特性面板里的计数。如果两边对不上基本就是分发漏了类型。另一条路是同一张图另存为 DXF用文本搜索定位实体类型比对起来更直观。这套工具的定位不是万能解析器而是把读写流程稳定下来项目迭代时改一处回归时能马上暴露影响面。Teigha 这套东西本身不复杂复杂的是你对对象模型边界的理解。我每次做依赖它的项目都会先建一个按版本分桶的样例目录把每张图的读取结果落成 JSON 基线后续升级库版本或者改封装时跑一遍对比就知道有没有被误伤。希望帮到你。本文还有配套的精品资源点击获取