常见陷阱与最佳实践
本章汇总 AutoCAD .NET 二次开发中最常见的问题和推荐做法,帮你少踩坑。
陷阱 1:事务外访问 DBObject
// ❌ 致命错误 —— 访问已释放的对象
Line line;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
line = tr.GetObject(id, OpenMode.ForRead) as Line;
tr.Commit();
}
double len = line.Length; // 💥 AccessViolationException!
// ✅ 事务内提取所需数据
double length;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Line line = tr.GetObject(id, OpenMode.ForRead) as Line;
length = line.Length; // 在事务内读取到内存
// 或持有 ObjectId,需要时重新 GetObject
tr.Commit();
}
Console.WriteLine(length);💡 思路:在事务内将需要的值提取为 C# 原生类型(
double,string,Point3d),事务外使用这些值。ObjectId是值类型,事务外也可持有。
陷阱 2:忘记 AddNewlyCreatedDBObject
// ❌ 新对象未纳入事务
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Circle circle = new Circle(center, Vector3d.ZAxis, 50);
modelSpace.AppendEntity(circle);
// 缺少 tr.AddNewlyCreatedDBObject(circle, true);
tr.Commit(); // 💥 eNullObjectId 或崩溃
}
// ✅ 正确
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Circle circle = new Circle(center, Vector3d.ZAxis, 50);
modelSpace.AppendEntity(circle);
tr.AddNewlyCreatedDBObject(circle, true); // 纳入事务
tr.Commit(); // ✅
}陷阱 3:忘写 Commit
// ❌ 事务未提交
using (Transaction tr = db.TransactionManager.StartTransaction())
{
Line line = tr.GetObject(id, OpenMode.ForWrite) as Line;
line.ColorIndex = 1;
// 忘记 tr.Commit() —— 离开 using 时自动回滚
}
// 颜色未改变!🚨 最隐蔽的 Bug:代码逻辑完全正确,但操作后毫无效果。原因通常是忘了
Commit()。在事务包装方法中将Commit()内建是好的实践。
陷阱 4:遍历中修改集合
// ❌ 在遍历模型空间时添加/删除实体
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
foreach (ObjectId id in ms) // 正在遍历
{
Entity ent = tr.GetObject(id, OpenMode.ForWrite) as Entity;
ent.Erase(); // 💥 遍历期间修改集合!
}
tr.Commit();
}
// ✅ 先收集 ObjectId,再批量操作
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTableRecord ms = (BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
var idsToModify = new List<ObjectId>();
foreach (ObjectId id in ms)
{
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (ent is Line)
idsToModify.Add(id);
}
foreach (ObjectId id in idsToModify)
{
Entity ent = tr.GetObject(id, OpenMode.ForWrite) as Entity;
ent.ColorIndex = 5;
}
tr.Commit();
}陷阱 5:对象打开模式不当
// ❌ 使用 ForWrite 读取不修改的对象
Circle circle = tr.GetObject(id, OpenMode.ForWrite) as Circle;
double radius = circle.Radius; // 只是读取,但用了 ForWrite
// 问题:不必要的写锁、产生撤销数据、文件膨胀
// ✅ 只读操作用 ForRead
Circle circle = tr.GetObject(id, OpenMode.ForRead) as Circle;
double radius = circle.Radius;⚡ 使用
ForRead而非ForWrite读取数据,显著减少 DWG 撤销数据大小并提高多文档并发性能。
陷阱 6:引用 Copy Local 未设为 False
// 问题:将 acmgd.dll / acdbmgd.dll 复制到了输出目录
// 症状:FileLoadException, 类型不匹配, 找不到方法
// 解决:项目引用 → 属性 → Copy Local = False🔬 原因:AutoCAD 的托管 DLL 是特定版本的原生 C++/CLI 包装,与 AutoCAD.exe 绑定的原生代码耦合。如果把 DLL 复制到输出目录,可能加载了错误版本的 DLL。
陷阱 7:忘记 DowngradeOpen 导致死锁
// ❌ 升级后未降级,长期持有写锁
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (needModify)
{
ent.UpgradeOpen();
ent.Layer = "NEW";
// 忘记 DowngradeOpen()
}
// ✅ 修改后降级
Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
if (needModify)
{
ent.UpgradeOpen();
ent.Layer = "NEW";
ent.DowngradeOpen();
}陷阱 8:事件处理器内存泄漏
// ❌ 事件未取消订阅,GC 无法回收
Document doc = Application.DocumentManager.MdiActiveDocument;
doc.CommandEnded += OnCommandEnded;
// 如果插件卸载时未 -=,EventSource 持有 Handler 引用 → 内存泄漏
// ✅ 实现 IExtensionApplication 管理生命周期
public class MyPlugin : IExtensionApplication
{
public void Initialize()
{
Application.DocumentManager.DocumentCreated += OnDocCreated;
// ...
}
public void Terminate()
{
Application.DocumentManager.DocumentCreated -= OnDocCreated;
// ...
}
}最佳实践清单
事务管理
- 💡 使用
using声明保证事务 Dispose - 💡 只读遍历用
StartOpenCloseTransaction提升性能 - 💡 包裹工具方法使
Commit()自动化,防止遗忘 - 💡 事务外只持有
ObjectId,不持有DBObject
实体操作
- 💡 添加新实体后立即调用
AddNewlyCreatedDBObject - 💡
as类型转换优于强制转换,配合 null 检查 - 💡 遍历 BTR 时,先收集 ID 列表,再操作
性能
- 💡 批量操作尽量用一个事务,避免逐对象开新事务
- 💡 遍历大量实体时用
ForRead而非ForWrite - 💡 用
OpenCloseTransaction进行只读查询 - 💡
ResultBuffer构建大数据时使用using
调试
- 💡 命令入口处 try-catch 并
WriteMessage输出异常信息 - 💡 使用
ed.WriteMessage而非Console.WriteLine做日志输出 - 💡 CAD 崩溃时检查 Windows 事件查看器 → 应用程序日志 → .NET Runtime 错误
部署
- 💡 Copy Local = False
- 💡 使用
.bundle包格式部署 - 💡 针对特定 AutoCAD 版本编译(标定
net48和引用版本) - 💡 发布前用 Release 模式编译
常见错误码速查
| 错误 | 可能原因 |
|---|---|
eNullObjectId |
忘记 AddNewlyCreatedDBObject |
eWasErased |
对象已被删除,但还持有其 ObjectId |
eInvalidOpenState |
对已被 Dispose 的对象操作 |
eWrongDatabase |
对象属于其他 Database(如侧载的 DWG) |
eNotThatKindOfClass |
类型转换错误(如把 Circle 当 Line) |
AccessViolationException |
事务外访问 DBObject 或空指针 |
InvalidOperationException |
在遍历中修改集合 |