简介:面向AutoCAD二次开发人员的Teigha(原OpenDwg/DWGdirect)开发资料包,专门解决不启动AutoCAD即可读写DWG文件的技术难题,也可为独立CAD工具链提供底层支持。资料附带完整的帮助文档,并提供VB.NET与C#两套原生工程源码,包含调用关键接口的读写示例,可直接参考或改造使用。压缩包共165个文件,核心为62个动态库与33个tx依赖文件,另含两套语言的源码文件、可执行示例程序以及工程配置,整体61.5MB,目录结构清晰明了、按模块分类便于定位。帮助说明配合示例程序,能辅助理解DWG文件的数据结构,减少配置与摸索时间。目前已有3826人学习下载,网上有关Teigha的开发资料本就稀少,这份整理能大幅节省检索与试错成本,适合需要深入了解DWG底层读取机制或构建独立CAD工具链的中高级开发者。
1. Teigha4 不是 Autodesk 官方库,但脱离 AutoCAD 读写 DWG 这件事它最省力
拿到 DWGdirect_NET_3_02、Teigha_Net_40010 这个资源包时,多数人的第一反应是建个工程、加上引用、照着示例跑一遍。结果通常是第一次打开 DWG 文件就抛异常——不是报缺少依赖,就是提示未初始化。原因很简单:Teigha 的 .NET 封装不是那种new Application()就能用的 COM 组件,它需要先注册平台服务、加载授权文件,再进入事务模型操作数据库。这个资源包解决的问题,恰好就是「在完全脱离 AutoCAD 进程的前提下,用 C# 或 vb.net 读写 DWG 文件」这件事。它适合三种人:要批量解析图纸做数据入库的、要做图纸在线预览或格式转换(比如 dwg 转 shp 前的预处理)的、以及想在服务端生成 DWG 文件给下游用的。这个包里最值钱的不是那几份 DLL,而是帮助文档和两套年代不同但 API 互补的 C# 原码。
2. 先把环境摆正:DLL 布局、授权文件与 .NET 版本匹配
Teigha 4.0 的 .NET 封装有个特点:托管程序集只是壳,真正干活的是同目录下的原生 DLL。很多人把 Teigha_Net_40010.dll 单独拷出来就开跑,结果运行时报DllNotFoundException,其实缺的是 TD_Mgd、TD_Db 这些原生库。这一章先把工程结构理清,再解决授权问题,最后说清 DWGdirect 和 Teigha_Net 两套 API 该怎么互相对照着看。
2.1 文件里四件套在工程中怎么摆
解压资源包后,你会看到四类东西:Teigha_Net_40010.dll(托管封装层)、TD_Mgd_4.0.0.x.dll + TD_Db_4.0.0.x.dll(原生核心库)、DWGdirect_NET_3_02 目录(旧版 .NET 封装,含 vb.net 和 C# 源码工程)、以及 .chm 格式的帮助文档。常见做法是建一个libs/目录,把这几个 DLL 都放进去,工程引用指向 Teigha_Net_40010.dll,并设置复制到输出目录。注意 TD_ 开头的原生 DLL 必须保持在 Teigha_Net_40010.dll 同一目录,否则加载托管库时找不到原生依赖。
平台目标那里有个隐藏规则:Teigha_Net 4.0.0.10 的原生库是区分 x86 和 x64 的。资源包里一般带两个变体,在工程属性里把「平台目标」显式设为 x64 或 x86,不要选 AnyCPU。选 AnyCPU 时,CLR 在 64 位系统上会以 x64 模式运行,但如果原生库路径解析到 x86 版本,直接抛BadImageFormatException,这一条后面在避坑章再展开。
2.2 授权注册:Teigha 第一个玄学点
Teigha 不是免费库,运行时需要授权文件(.lic)。包内通常会附带一份评估授权,但你需要知道它加载机制。最稳的做法是用环境变量指定授权路径,然后在代码里初始化。C# 端完整的初始化代码:
using Teigha.Runtime; using Teigha.DatabaseServices; class TeighaHost : HostAppServices { // Teigha 要求继承 HostAppServices 并重写下面两个属性 public override string HostName => "MyDwgTool"; public override string ProductName => "DwgReadWrite"; } // 程序启动时执行一次 RuntimeManager.Initialize(); // 设置授权文件所在目录,也可以在系统环境变量里配 TEIGHA_LICENSE_DIR string licDir = @"D:\teigha_lic\"; HostAppServices.Current = new TeighaHost(); GetLicenseManager().SetLicensePath(licDir); GetLicenseManager().SetRuntimePath(AppDomain.CurrentDomain.BaseDirectory);提示:
GetLicenseManager()是Teigha.Runtime.RuntimeManager类的静态方法,不是实例方法。忘记设置授权路径时,打开文件不会报「无授权」,而是报无关的文件格式异常,这是最容易迷惑人的地方。
2.3 DWGdirect 与 Teigha_Net 命名空间差异
DWGdirect_NET_3_02 是较老的一代封装,API 风格偏 COM,很多方法名是GetXxx()形式;Teigha_Net 4.0 开始全面转向System.IDisposable风格,大量使用Transaction。读旧源码时要在两套间做映射。核心对应关系如下:
| 功能 | DWGdirect_NET_3_02 | Teigha_Net_40010 |
|---|---|---|
| 打开数据库 | DwgDatabase.OpenReadOnly(path) | DwgDatabase.ReadDwgFile(path) |
| 开始事务 | db.TransactionManager.StartTransaction() | db.TransactionManager.StartTransaction() |
| 取模型空间 | db.GetModelSpace() | db.GetModelSpaceId()再Transaction.GetObject() |
| 遍历实体 | ms.GetIterator() | BlockTableRecord的BlockTableRecord.GetEnumerator() |
| 保存文件 | db.SaveAs(path) | db.SaveAs(path, DwgVersion.Current) |
最新版封装里很多方法接收ObjectId而不是直接返回对象,这是从「对象句柄」走向「事务内解析」的设计,初看别扭,但能让你明确每个对象的生命周期。帮助文档是 .chm 格式,如果双击打开是空白,先右键属性→解除锁定,再用管理员身份打开。
3. 读 DWG:从打开数据库到遍历模型空间的完整 C# 流程
读文件是 Teigha 最日常的用途。无论你是做图纸数据抽取、dwg 转 shp 的预处理,还是做 dwg 在线预览的前端喂数据,本质都是同一步:打开数据库,拿到模型空间,遍历实体并提取几何与属性。区别只在于提取后你怎么用。下面按完整可复现的顺序来。
3.1 打开文件与事务骨架
Teigha 的所有对象访问都必须发生在事务内。读操作同样要开事务,只是打开模式为OpenMode.ForRead。下面的骨架代码是后续所有提取操作的地基:
using Teigha.Runtime; using Teigha.DatabaseServices; string filePath = @"C:\drawings\test.dwg"; using (DwgDatabase db = new DwgDatabase(true, null)) { db.ReadDwgFile(filePath, FileShare.Read, true, ""); // 参数说明:true 表示只读不写;FileShare.Read 允许其他进程同时读同一文件 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); // 遍历记录里的所有实体 ID foreach (ObjectId eId in ms) { Entity ent = (Entity)tr.GetObject(eId, OpenMode.ForRead); // 这里按类型分发处理 DumpEntity(ent); } tr.Commit(); } }这段代码里最关键的是ReadDwgFile的前两个参数:FileShare.Read保证你读取时不会被文件占用卡死,第三个参数""是密码,没加密就传空串。Transaction的Commit()对纯读操作不是必须的,但我习惯写上,避免以后改成写操作时漏掉。遍历模型空间拿到的ObjectId是虚线引用,必须在事务内GetObject才能变成真实实体。
3.2 按实体类型提取几何与属性
实体类型决定了你能提取什么。实际图纸里最常见的是 Line、Circle、Polyline、DBText(单行文字)、MText、Insert(块引用)。用类型分发处理,最直观的是switch加类型判断:
private void DumpEntity(Entity ent) { // 每个实体都有的通用属性:图层、颜色、线型 string layer = ent.Layer; string color = ent.Color.ToString(); switch (ent) { case Line line: // 直线端点坐标,单位与文件设置相关 var sp = line.StartPoint; var ep = line.EndPoint; Console.WriteLine($"LINE on {layer}: ({sp.X},{sp.Y}) -> ({ep.X},{ep.Y})"); break; case Circle cir: Console.WriteLine($"CIRCLE on {layer}: center=({cir.Center.X},{cir.Center.Y}) R={cir.Radius}"); break; case Polyline pl: // 多段线顶点数量,顶点坐标存放在 Vertices 集合里 for (int i = 0; i < pl.NumberOfVertices; i++) { var v = pl.GetPoint3dAt(i); Console.WriteLine($" vertex {i}: {v.X},{v.Y}"); } break; case DBText txt: // 单行文字内容,注意编码是 Unicode Console.WriteLine($"TEXT on {layer}: \"{txt.TextString}\" at ({txt.Position.X},{txt.Position.Y})"); break; case Insert ins: // 块引用:拿到块名和插入点 BlockTableRecord btr = (BlockTableRecord)ins.GetTransformedBlockRecord(); Console.WriteLine($"INSERT {btr.Name} at ({ins.Position.X},{ins.Position.Y})"); break; } }注意Polyline.GetPoint3dAt()接收的是索引,不是循环变量本身,越界会抛ArgumentOutOfRangeException。块引用的Position给出的是插入点,但如果你要块内嵌套文字的位置,得用ins.BlockTransform把块内坐标系换算到世界坐标系。这是批量处理图纸时最容易被忽略的细节。
3.3 坐标单位换算:很多报错的根源
Teigha 返回的坐标不是毫米,也不是英寸,而是文件内部的线性单位。判断依据在DwgDatabase上,比较隐蔽。给出一段换算逻辑:
// 获取文件的单位配置 UnitConversion.GetUnitsForDrawing(db.InsUnits, out double unitScale); // db.InsUnits 的类型是 UnitsValue,常用值:Millimeters=4, Centimeters=5, Inches=1 // unitScale 表示「1 内部单位 = 多少英寸」,毫米文件通常返回 0.0393701 Console.WriteLine($"单位换算系数: {unitScale}");UnitConversion在Teigha.Geometry命名空间里。读文件时如果不做这一步,拿到的坐标可能整体缩小 25.4 倍或放大 25.4 倍,提取出来的几何在叠加到 GIS(比如做 dwg 转 shp)时会整体偏移。我的习惯是所有数据统一换算成毫米后再对外输出,避免下游系统各自猜单位。
4. 写 DWG:从空库创建到另存为的完整流程
程序化生成 DWG 是 Teigha 另一个主战场:自动导出图纸、生成管线路由图、批量出竣工图。写文件和读文件的核心差异在于打开模式和对事务的提交要求,另外图层、线型、文字样式的创建顺序不能乱。这一章用一个「从零建图」的例子把整个链路走通。
4.1 创建数据库并准备图层
写 DWG 的第一步不是开文件,而是 new 一个空数据库,然后往块表和图层表里加定义:
using (DwgDatabase db = new DwgDatabase(true, null)) { using (Transaction tr = db.TransactionManager.StartTransaction()) { // 拿到块表(读写模式) BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForWrite); // 拿到模型空间记录 BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); // 创建图层:先取图层表,再追加记录 LayerTable lt = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForWrite); if (!lt.Has("管线层")) { LayerTableRecord ltr = new LayerTableRecord { Name = "管线层", Color = Color.FromColorIndex(ColorMethod.ByAci, 1) // 索引色 1 = 红色 }; lt.Add(ltr); tr.AddNewlyCreatedDBObject(ltr, true); } // 事务提交,把图层定义写入数据库 tr.Commit(); } }关键点是tr.AddNewlyCreatedDBObject(ltr, true)——true表示这个对象已由事务接管,出了事务也能正确落盘。如果漏掉这一行,图层会静默丢失,文件保存后打开没有任何图层。Color.FromColorIndex(ColorMethod.ByAci, 1)用的是 AutoCAD 索引色,1 是红、2 是黄、3 是绿,0 是随块,256 是随层。
4.2 添加几何实体到模型空间
有了图层,就可以在模型空间里加实体。每个实体创建后同样要AddNewlyCreatedDBObject,否则报eNoDatabase:
using (Transaction tr = db.TransactionManager.StartTransaction()) { BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); // 直线:两个三维点定义 Line line = new Line(new Point3d(0, 0, 0), new Point3d(1000, 800, 0)) { Layer = "管线层" }; ms.AppendEntity(line); tr.AddNewlyCreatedDBObject(line, true); // 圆:圆心 + 半径 Circle cir = new Circle { Center = new Point3d(500, 400, 0), Radius = 120, Layer = "管线层" }; ms.AppendEntity(cir); tr.AddNewlyCreatedDBObject(cir, true); // 单行文字:指定插入点、字高、旋转角,字符串可直接传中文 DBText txt = new DBText { Position = new Point3d(300, 200, 0), Height = 50, Rotation = 0, TextString = "测试文字", Layer = "管线层" }; ms.AppendEntity(txt); tr.AddNewlyCreatedDBObject(txt, true); tr.Commit(); }AppendEntity之后必须立刻AddNewlyCreatedDBObject,顺序反了会报对象已在数据库之外的错。DBText的TextString是 Unicode 编码,中文不用额外转码,但要注意生成的文件如果被旧版 CAD(比如 R14)打开,中文可能显示为乱码——这是字体映射问题,不是编码问题。
4.3 保存与版本兼容
写完后保存,SaveAs需要指定版本。Teigha 4.0 支持的版本枚举如下:
| 枚举值 | 对应 DWG 版本 | 典型使用场景 |
|---|---|---|
DwgVersion.R13 | AutoCAD R13 | 极老系统兼容,不推荐 |
DwgVersion.R14 | AutoCAD R14 | 老设计院仍在使用 |
DwgVersion.R2000 | AutoCAD 2000 | 最稳妥的兼容格式 |
DwgVersion.R2004 | AutoCAD 2004 | 多数测绘工具首选 |
DwgVersion.R2007 | AutoCAD 2007 | 高版本特性可用 |
DwgVersion.R2010 | AutoCAD 2010 | 与新版 GIS 工具适配较好 |
DwgVersion.Current | 当前版本 | 不可用于跨版本分发 |
// 保存路径和版本 string outPath = @"C:\output\generated.dwg"; db.SaveAs(outPath, DwgVersion.R2004);注意:
SaveAs之后数据库的DwgVersion属性会变成目标版本,如果还要继续在这个数据库上操作再保存,需要重新确认版本。低版本格式对实体属性有约束,最典型的是 R2004 及以下不支持渐变色填充和部分阴影类型,写入时 Teigha 不会报错,但真正打开文件时这些对象会显示异常。
5. 避坑:Teigha4 读写 DWG 的五个真实翻车点
Teigha 这套库的文档不算厚,很多坑是跑出来的。下面五条是我实际调试中遇到过的,按「现象→原因→解决」写,直接可对照。
5.1 授权文件没生效,打开文件报文件格式错误
现象:db.ReadDwgFile(path, FileShare.Read, true, "")抛异常,信息是eFileVersionUnknown或eInvalidDwgFile,但你确认文件能用 AutoCAD 正常打开。
原因:Teigha 找不到合法的授权文件。它把授权校验放在文件解析的早期,授权失败时返回的却是文件解析错误,误导性极强。
解决:检查SetLicensePath指向的目录是否存在.lic文件;确认环境变量TEIGHA_LICENSE_DIR没覆盖你的代码路径。在Initialize()之后调用GetLicenseManager().IsLicenseValid()打点日志,先确认授权再碰文件。从那以后我每个新环境的第一个测试用例都是「读一个已知好文件 + 打印授权状态」,两步都过了再谈业务。
5.2 平台位数不匹配,一进事务就崩
现象:程序启动正常,RuntimeManager.Initialize()过了,但首次StartTransaction()或者创建DwgDatabase时抛TypeInitializationException,内部是DllNotFoundException或BadImageFormatException。
原因:Teigha_Net 托管层没有把原生 DLL 的位数绑定到程序集引用上。你的程序集编译为 AnyCPU 时,运行环境可能加载了错误位数的 TD_ 原生 DLL。
解决:工程属性→生成→平台目标强制 x64(或根据你拿到的原生库版本强制 x86);同时确认libs目录里只放一套原生 DLL,别把 x86 和 x64 混在同一目录——混放时 .NET 加载器经常挑到错的那个。
5.3 事务没释放,处理几百个文件后内存暴涨
现象:单进程处理数百个 DWG 后内存占用达到 2GB 以上,最终抛OutOfMemoryException。表面看像是 Teigha 自身泄漏。
原因:多数情况不是 Teigha 泄漏,而是事务和对象没有及时 Dispose。Teigha 的Transaction.GetObject()返回的对象不是 .NET 托管对象,而是一层 P/Invoke 包装,GC 回收不及时,必须显式释放。
解决:所有Transaction用using包裹;GetObject拿到的实体,如果批量场景,及时调用.Dispose()。经验值是:读取一个 1 万实体的文件,如果不释放中间对象,内存峰值是释放后的 8 到 10 倍。我在批量遍历里会每处理 200 个实体做一次GC.Collect(),虽然粗暴但有效,生产环境没那么讲究。
5.4 读到的坐标整体偏小/偏大
现象:提取的直线端点坐标与 CAD 里查询的坐标相差一个数量级,比如 CAD 显示 1000mm,Teigha 读出来是 39.37。
原因:没做单位换算。文件本身是英寸制(InsUnits = Inches),Teigha 按内部单位返回,你没乘换算系数。
解决:读取文件后先检查db.InsUnits,按 3.3 节的换算逻辑统一到目标单位。更保险的做法是同时读取UnitConversion.GetUnitsForDrawing得到的换算系数,别硬编码 25.4 或 0.03937,因为部分文件是厘米或英尺制。
5.5 抄 vb.net 源码时把 OpenMode 写错,报ePermissionDenied
现象:在 C# 里照搬 vb.net 示例逻辑,用ForRead模式打开对象后试图修改属性,抛ePermissionDenied。
原因:vb.net 示例里大量对象在事务开始时以ForWrite打开,而 C# 移植时把枚举值抄成了ForRead。Teigha 对权限检查非常严格,ForRead的对象任何写操作都会直接抛错,没有缓冲。
解决:明确区分读流程和写流程。写操作的所有对象——块表、模型空间、图层表、具体实体——全部用OpenMode.ForWrite打开;但在同一个事务里对同一个对象重复GetObject且前后模式不一致时,后一次会覆盖前一次的模式,容易引发隐藏 bug。我一般一个事务只做一件事:要么纯读提取,要么纯写生成,不混合。
6. 进阶技巧:反射批量导出实体快照,顺带做读写的自校验
最后一个技巧是把整章内容串起来:写一个批量工具,跑完一整个目录的 DWG,把实体快照导成 CSV 用于交接或数据入库,并反向验证生成文件的完整性。这个技巧的实用场景是:你部署了一个基于 Teigha 的服务,跑了几千个文件,怎么在没人盯着的时候知道哪些文件读失败了、哪些写坏了。
思路是分两步。第一步,遍历目标目录,对每个 DWG 做「读→提取快照→保存 CSV」;第二步是写完的文件用同一个读取流程再读一遍,并抽样CheckDwgFile()验证结构完整性。CSV 导出里有一个实用技巧:用反射遍历实体的公共几何属性,省去逐个实体手写字段映射的工作量。
static void ExportDwgSnapshot(string dwgPath, string csvOut) { StringBuilder sb = new StringBuilder(); sb.AppendLine("EntityType,Layer,X,Y,Radius,Text"); using (DwgDatabase db = new DwgDatabase(true, null)) { db.ReadDwgFile(dwgPath, FileShare.Read, true, ""); 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 e = (Entity)tr.GetObject(id, OpenMode.ForRead); string line = ""; switch (e) { case Circle c: line = $"Circle,{e.Layer},{c.Center.X:F2},{c.Center.Y:F2},{c.Radius:F2},"; break; case DBText t: line = $"Text,{e.Layer},{t.Position.X:F2},{t.Position.Y:F2},,{t.TextString}"; break; default: // 读取公共属性最省事,不会有遗漏 var props = e.GetType().GetProperties() .Where(p => p.PropertyType == typeof(double)) .Select(p => p.GetValue(e)?.ToString() ?? ""); line = $"{e.GetType().Name},{e.Layer},,,{string.Join("|", props)}"; break; } sb.AppendLine(line); } tr.Commit(); } } File.WriteAllText(csvOut, sb.ToString(), Encoding.UTF8); }这段代码里的反射兜底逻辑非常实用:常见实体都有专门分支输出干净字段;不认识的实体类型走反射分支,把它的所有 double 属性抓出来拼接,保证不丢信息。string.Join("|", props)用竖线做属性分隔,避免 CSV 里出现逗号冲突。
第二步的自校验,我习惯在批量工具末尾加一轮反读检查——生成完的 DWG 全部重新打开一次,跑db.CheckDwgFile()看结果:
// 反读校验:确认生成的文件没有结构性问题 using (DwgDatabase dbVerify = new DwgDatabase(true, null)) { dbVerify.ReadDwgFile(csvOut.Replace(".csv", ".dwg"), FileShare.Read, true, ""); // CheckDwgFile 返回 0 表示无错误;非 0 是错误位掩码 int errCode = dbVerify.CheckDwgFile(); Console.WriteLine($"verify result: 0x{errCode:X}"); }CheckDwgFile()是 Teigha 给的一个低成本完整性检查接口,比用 AutoCAD 打开快得多,适合批量场景。它在文件体结构损坏时返回非零值,常见的 0x1 表示文件头损坏,0x8 表示对象表损坏。注意它不校验语义问题——比如文字内容是否合理、图层是否被引用——那还是得靠业务规则过滤。从那以后我每次部署新的 Teigha 工具,都会先跑一遍「批量生成 + 反读校验 + CSV 比对」,三个流程走完才敢交给下游系统,希望帮到你。这套方法延续到今天,处理 DWG 相关需求时已经成了固定动作。
本文还有配套的精品资源,点击获取