1. 中望CAD扩展记录开发概述
在中望CAD二次开发领域,扩展记录(XRecord)是一种比传统扩展数据(XData)更强大的数据存储机制。作为一名长期从事CAD二次开发的工程师,我发现扩展记录在实际项目中具有不可替代的价值。与扩展数据相比,扩展记录突破了256字节的限制,支持更丰富的数据类型,并且通过字典结构实现了更好的数据组织和管理。
扩展记录的核心优势体现在三个方面:
- 容量无限制:不像扩展数据受限于固定大小的缓冲区
- 类型更丰富:支持整型、实型、字符串、点、向量等多种数据类型
- 安全性更高:数据存储在扩展字典中,不易被意外修改
在实际工程应用中,我经常使用扩展记录来存储以下类型的数据:
- 构件参数化设计信息
- 产品制造数据(PMI)
- 自定义对象关联关系
- 版本控制元数据
2. 扩展记录实现原理详解
2.1 扩展记录的存储结构
扩展记录采用字典-记录的两级存储结构。每个实体可以拥有一个扩展字典(Extension Dictionary),字典中可以包含多个命名记录(XRecord)。这种结构类似于文件系统中的文件夹和文件关系。
mermaid复制graph TD
A[实体] --> B[扩展字典]
B --> C[XRecord1]
B --> D[XRecord2]
B --> E[...]
注意:在实际开发中,一个实体应该只创建一个扩展字典,但可以在其中存储多个扩展记录。
2.2 数据类型支持对比
| 数据类型 | 扩展数据(XData) | 扩展记录(XRecord) |
|---|---|---|
| 整数 | ✓ | ✓ |
| 实数 | ✓ | ✓ |
| 字符串 | ✓ | ✓ |
| 点 | ✓ | ✓ |
| 向量 | ✓ | ✓ |
| 图层ID | ✗ | ✓ |
| 块参照 | ✗ | ✓ |
| 二进制数据 | 有限支持 | 完全支持 |
| 嵌套数据结构 | ✗ | ✓ |
从表格可以看出,扩展记录在数据类型支持上明显优于传统扩展数据。特别是在处理复杂对象引用和嵌套数据时,扩展记录表现出更大的灵活性。
3. 扩展记录实战开发
3.1 创建扩展记录
下面是一个完整的扩展记录创建示例,包含错误处理和内存管理:
cpp复制void createXRecord(const ZcDbObjectId& entId, const ZTCHAR* recordName)
{
// 打开实体以写入模式
ZcDbEntity* pEnt = nullptr;
if (acdbOpenObject(pEnt, entId, ZcDb::kForWrite) != Zcad::eOk)
{
acutPrintf(_T("无法打开实体\n"));
return;
}
// 获取或创建扩展字典
ZcDbObjectId dictId = pEnt->extensionDictionary();
if (dictId.isNull())
{
pEnt->createExtensionDictionary();
dictId = pEnt->extensionDictionary();
}
pEnt->close();
// 打开字典以写入模式
ZcDbDictionary* pDict = nullptr;
if (acdbOpenObject(pDict, dictId, ZcDb::kForWrite) != Zcad::eOk)
{
acutPrintf(_T("无法打开扩展字典\n"));
return;
}
// 检查记录是否已存在
if (pDict->has(recordName))
{
acutPrintf(_T("记录已存在,将覆盖\n"));
pDict->remove(recordName);
}
// 创建新记录
ZcDbXrecord* pXrec = new ZcDbXrecord;
ZcDbObjectId xrecId = pDict->setAt(recordName, pXrec);
pXrec->close();
pDict->close();
// 准备数据
ZcResBuf* pRb = acutBuildList(
ZcDb::kDxfReal, 3.14159,
ZcDb::kDxfText, _T("示例数据"),
ZcDb::kDxfInt32, 42,
RTNONE
);
// 写入数据到记录
if (acdbOpenObject(pXrec, xrecId, ZcDb::kForWrite) == Zcad::eOk)
{
pXrec->setFromRbChain(*pRb);
pXrec->close();
}
acutRelRb(pRb);
}
这段代码展示了完整的扩展记录创建流程,包含以下几个关键点:
- 实体和字典的打开与关闭操作
- 内存资源的正确释放
- 错误条件的全面检查
- 数据的结构化存储
3.2 读取扩展记录
读取扩展记录时需要注意数据类型的解析顺序:
cpp复制void readXRecord(const ZcDbObjectId& entId, const ZTCHAR* recordName)
{
// 获取扩展字典
ZcDbEntity* pEnt = nullptr;
if (acdbOpenObject(pEnt, entId, ZcDb::kForRead) != Zcad::eOk)
return;
ZcDbObjectId dictId = pEnt->extensionDictionary();
pEnt->close();
if (dictId.isNull())
{
acutPrintf(_T("实体没有扩展字典\n"));
return;
}
// 打开字典
ZcDbDictionary* pDict = nullptr;
if (acdbOpenObject(pDict, dictId, ZcDb::kForRead) != Zcad::eOk)
return;
// 获取记录
ZcDbObjectId xrecId;
if (!pDict->getAt(recordName, xrecId))
{
pDict->close();
acutPrintf(_T("找不到指定记录\n"));
return;
}
pDict->close();
// 读取记录数据
ZcDbXrecord* pXrec = nullptr;
if (acdbOpenObject(pXrec, xrecId, ZcDb::kForRead) != Zcad::eOk)
return;
ZcResBuf* pRb = nullptr;
if (pXrec->rbChain(pRb) == Zcad::eOk && pRb != nullptr)
{
for (ZcResBuf* p = pRb; p != nullptr; p = p->next())
{
switch (p->restype)
{
case ZcDb::kDxfInt32:
acutPrintf(_T("整型: %d\n"), p->resval.rlong);
break;
case ZcDb::kDxfReal:
acutPrintf(_T("实数: %g\n"), p->resval.rreal);
break;
case ZcDb::kDxfText:
acutPrintf(_T("字符串: %s\n"), p->resval.rstring);
break;
// 其他数据类型处理...
}
}
acutRelRb(pRb);
}
pXrec->close();
}
重要提示:解析数据时必须严格按照写入顺序读取,因为扩展记录不存储类型标识信息,完全依赖应用程序维护数据结构的正确性。
4. 高级应用技巧
4.1 数据版本控制
在实际项目中,我推荐为扩展记录添加版本信息,便于后续兼容性处理:
cpp复制void writeVersionedData(ZcDbXrecord* pXrec)
{
const int CURRENT_VERSION = 2;
ZcResBuf* pRb = acutBuildList(
ZcDb::kDxfInt32, CURRENT_VERSION, // 版本标记
ZcDb::kDxfText, _T("DataV2"), // 数据标识
ZcDb::kDxfReal, 1.234,
RTNONE
);
pXrec->setFromRbChain(*pRb);
acutRelRb(pRb);
}
void readVersionedData(ZcDbXrecord* pXrec)
{
ZcResBuf* pRb = nullptr;
if (pXrec->rbChain(pRb) == Zcad::eOk)
{
int version = 1; // 默认版本
if (pRb->restype == ZcDb::kDxfInt32)
{
version = pRb->resval.rlong;
pRb = pRb->next(); // 跳过版本号
}
// 根据版本处理数据
switch (version)
{
case 1: processV1Data(pRb); break;
case 2: processV2Data(pRb); break;
}
acutRelRb(pRb);
}
}
4.2 二进制数据存储
扩展记录非常适合存储二进制数据,如图片、加密信息等:
cpp复制void storeBinaryData(ZcDbXrecord* pXrec, const BYTE* data, size_t size)
{
ZcResBuf* pRb = acutBuildList(
ZcDb::kDxfBinaryChunk, size, data,
RTNONE
);
pXrec->setFromRbChain(*pRb);
acutRelRb(pRb);
}
5. 性能优化建议
-
批量操作:当需要处理大量实体的扩展记录时,先收集所有实体ID,然后在一个事务中完成所有操作。
-
缓存设计:对频繁访问的扩展记录,考虑实现缓存机制,避免重复打开/关闭操作。
-
数据结构优化:
- 将经常一起访问的数据放在同一个记录中
- 对大型数据考虑分块存储
- 使用二进制格式存储大量数值数据
-
错误处理:始终检查数据库操作返回值,特别是在生产环境中。
cpp复制// 好的错误处理示例
Zcad::ErrorStatus es = acdbOpenObject(pEnt, entId, ZcDb::kForWrite);
if (es != Zcad::eOk)
{
logError(es, _T("打开实体失败"));
return;
}
6. 常见问题解决方案
6.1 数据损坏问题
症状:读取扩展记录时程序崩溃或数据错乱
排查步骤:
- 检查数据读取顺序是否与写入完全一致
- 验证缓冲区释放是否正确(使用acutRelRb)
- 检查多线程访问同步问题
6.2 性能瓶颈
优化方案:
- 对高频访问记录实现缓存
- 将多个小记录合并为大记录
- 使用二进制替代文本格式
6.3 兼容性问题
解决策略:
- 添加版本标识字段
- 实现数据迁移路径
- 提供默认值处理逻辑
7. 实际工程经验分享
在最近的一个机械设计项目中,我们使用扩展记录实现了以下功能:
- 参数化零件库:将零件的参数化信息存储在扩展记录中,实现动态生成
cpp复制struct PartParams {
double length;
double width;
int material;
// ...
};
void storePartParams(ZcDbXrecord* pXrec, const PartParams& params)
{
ZcResBuf* pRb = acutBuildList(
ZcDb::kDxfReal, params.length,
ZcDb::kDxfReal, params.width,
ZcDb::kDxfInt32, params.material,
RTNONE
);
pXrec->setFromRbChain(*pRb);
acutRelRb(pRb);
}
-
设计变更追踪:记录每个重要设计变更的时间和作者
-
制造信息附加:存储加工精度、表面处理等制造要求
经过实际验证,扩展记录在这种应用场景下表现出以下优势:
- 数据与图形实体保持同步移动/复制
- 不影响原有DWG文件兼容性
- 查询效率高于外部数据库方案
在实现过程中,我们也总结了一些有价值的经验:
- 为每个记录类型定义明确的命名规范
- 限制单个记录大小在1MB以内以保证性能
- 定期检查并清理不再使用的记录
- 为关键操作添加事务支持