标签: ObjectARX, GRX, AutoCAD, C++, 智能指针, 内存管理


引言

在 CAD 二次开发领域,ObjectARX(AutoCAD Runtime eXtension)是最强大的 C++ API。然而,其对象系统设计之精妙往往让开发者感到困惑。本文将深入剖析 ObjectARX 的核心对象系统——从 AcRxObject 运行时类型系统AcDbObject 资源管理,并对比国产 CAD 平台 GRX(GstarCAD Runtime eXtension)的实现差异,特别是 GRX 中智能指针的设计哲学。


一、RxObject 的引入原因:为什么要重新发明 RTTI?

1.1 历史背景:1990 年代的 C++ 困境

ObjectARX 诞生于 1990 年代,当时的 C++ 标准尚未成熟:

  • 没有标准的 RTTItype_info 在不同编译器间不兼容
  • 没有反射机制:无法运行时获取类信息
  • 跨 DLL 边界问题:MFC/VC++ 的 CRuntimeClass 不跨 DLL
  • 没有智能指针:裸指针管理是常态

1.2 五大核心问题

问题 C++ 原生局限 ObjectARX 解决方案
跨 DLL 类型失效 dynamic_cast 依赖编译器 RTTI 自定义全局 AcRxClass 字典
无法动态创建对象 new 需要编译时确定类型 运行时工厂 AcRxClass::create()
类型转换不安全 static_cast 危险,dynamic_cast 有开销 自定义 isKindOf() + cast()
扩展需继承 违反开闭原则 协议扩展 queryX()
版本兼容难题 无法处理未知类 版本号 + 代理对象机制

1.3 设计哲学:元对象系统

AcRxObject 本质上是一个元对象系统(Meta-Object System),类似于 Qt 的 moc 机制,但完全在 C++ 宏层面实现:

// 运行时类描述符,不依赖编译器
class AcRxClass {
    const ACHAR* name();           // 类名
    AcRxClass*   myParent();       // 父类
    AcRxObject*  create();         // 工厂方法
    // ...
};

二、RxObject 的基本协议

2.1 核心组件

// 基类:所有 ARX 对象的根
class AcRxObject {
public:
    virtual AcRxClass* isA() const = 0;           // 获取类描述符
    virtual bool isKindOf(const AcRxClass*) const; // 类型检查
    AcRxObject* queryX(const AcRxClass* protocol); // 协议扩展
};

// 类描述符:运行时类型信息
class AcRxClass {
    const ACHAR* name() const;      // 类名
    AcRxClass* myParent() const;     // 父类
    AcRxObject* create() const;      // 创建实例
    void addX(AcRxClass* protocol, AcRxObject* impl); // 添加协议
};

2.2 关键宏机制

ACRX_DECLARE_MEMBERS:在类声明中使用,生成运行时方法

class MyClass : public AcRxObject {
public:
    ACRX_DECLARE_MEMBERS(MyClass);  // 展开为 isA(), desc(), cast() 等
};

ACRX_DXF_DEFINE_MEMBERS:在实现文件中定义类信息

ACRX_DXF_DEFINE_MEMBERS(
    MyClass,                      // 类名
    AcRxObject,                   // 父类
    AcDb::kDHL_CURRENT,          // DWG 版本
    AcDb::kMReleaseCurrent,      // 维护版本
    AcDbProxyEntity::kNoOperation, // 代理标志
    MYCLASS,                      // DXF 名称
    "MyApplication\n"             // 应用程序名
)

2.3 核心 API 速查表

方法 用途 示例
isA() 获取对象的运行时类 pObj->isA()->name()"AcDbLine"
isKindOf() 检查是否继承自某类 pObj->isKindOf(AcDbCurve::desc())
desc() 获取类描述符(静态) AcDbLine::desc()
cast() 安全类型转换 AcDbLine::cast(pObj)
create() 动态创建对象 AcDbLine::desc()->create()
queryX() 协议扩展查询 pObj->queryX(IMyProtocol::desc())

2.4 使用示例

示例 1:运行时类型识别

// 从 DWG 读取到未知对象
AcDbObjectId objId;
AcDbObject* pObj;
acdbOpenObject(pObj, objId, AcDb::kForRead);

// 运行时识别类型
AcRxClass* pClass = pObj->isA();
acutPrintf(L"类型: %s\n", pClass->name());  // "AcDbLine"

// 检查是否是曲线类
if (pObj->isKindOf(AcDbCurve::desc())) {
    AcDbCurve* pCurve = AcDbCurve::cast(pObj);
    if (pCurve) {
        double length = pCurve->getDistAtParam(1.0);
    }
}

pObj->close();

示例 2:动态对象创建(DWG 反序列化)

// 从 DWG 文件读取到 "AcDbLine" 字符串
const wchar_t* className = L"AcDbLine";

// 通过类名获取类描述符
AcRxClass* pClass = acrxFindAcRxClass(className);
if (pClass) {
    // 动态创建实例(不需要知道 AcDbLine 的定义!)
    AcRxObject* pObj = pClass->create();
    
    // 类型安全转换
    AcDbLine* pLine = AcDbLine::cast(pObj);
    if (pLine) {
        // 使用 pLine...
    }
}

示例 3:协议扩展机制

// 定义协议接口
class ICustomRender : public AcRxObject {
public:
    ACRX_DECLARE_MEMBERS(ICustomRender);
    virtual void render() = 0;
};

// 实现扩展
class CustomRenderImpl : public ICustomRender {
    ACRX_DECLARE_MEMBERS(CustomRenderImpl);
    void render() override {
        acutPrintf(L"自定义渲染!\n");
    }
};

// 注册扩展(无需修改 AcDbEntity 源码!)
void init() {
    CustomRenderImpl* pImpl = new CustomRenderImpl;
    AcDbEntity::desc()->addX(ICustomRender::desc(), pImpl);
}

// 使用扩展
void use(AcDbEntity* pEnt) {
    ICustomRender* pRender = ICustomRender::cast(
        pEnt->queryX(ICustomRender::desc()));
    if (pRender) {
        pRender->render();
    }
}

三、DbObject 的资源管理

3.1 对象所有权模型

ObjectARX 使用数据库托管模式:

┌─────────────────────────┐
│      你的代码            │
│  new AcDbLine()         │ ← 创建
│  pSpace->appendAcDbEntity(objId, pLine) │ ← 移交所有权
│  pLine->close()         │ ← 释放访问权
└─────────────────────────┘
            ↓
┌─────────────────────────┐
│    AutoCAD 数据库        │
│  • 管理对象生命周期       │
│  • 处理序列化 (DWG/DXF)   │
│  • 文档关闭时统一释放      │
└─────────────────────────┘

3.2 黄金法则

场景 操作 后果(违规)
对象已添加到 DB 调用 close() delete 导致崩溃或双重释放
对象未添加到 DB 必须 delete 内存泄漏
临时访问对象 acdbOpenObject() + close() 裸指针悬挂
持久引用 使用 AcDbObjectId 指针失效

3.3 Smart Pointer:RAII 救星

头文件: dbobjptr.hdbobjptr2.h

// 基础版:自动 open/close
AcDbObjectPointer<AcDbLine> pLine(objId, AcDb::kForRead);
if (pLine.openStatus() == Acad::eOk) {
    // 使用 pLine->methods()
    double len = pLine->length();
}  // 析构时自动 close()

// 增强版:避免打开冲突
AcDbSmartObjectPointer<AcDbEntity> pEnt(objId, AcDb::kForRead);
// 如果对象已以兼容模式打开,不会重复打开

预定义 typedef(方便使用):

AcDbEntityPointer            // AcDbObjectPointer<AcDbEntity>
AcDbBlockTablePointer        // AcDbObjectPointer<AcDbBlockTable>
AcDbLayerTablePointer        // AcDbObjectPointer<AcDbLayerTable>
// ... 更多在 dbobjptr.h

3.4 对象生命周期状态图

创建 (new) → 未托管 → 添加到 DB (appendAcDbEntity)
                 ↓              ↓
             必须 delete()    调用 close()
                 ↓              ↓
               释放内存      数据库托管
                              ↓
                         文档关闭时销毁

四、GRX 中的 GcDbObject 对象实现

4.1 GRX 与 ObjectARX 的关系

GRX(GstarCAD Runtime eXtension)是浩辰 CAD 平台的 C++ API,完全兼容 ObjectARX,但内部实现有差异:

特性 ObjectARX GRX
运行时系统 AcRxObject GcRxObject
数据库对象 AcDbObject GcDbObject
智能指针 AcDbObjectPointer GcDbObjectPointer, GcSmartPtr
计数器 需配合数据库管理 Impl 自带引用计数

4.2 GcDbObject 的核心优势:内置计数器

关键区别GcDbObject 的 Impl 实现自带引用计数器,这带来了根本性改进:

// GRX 中 GcDbObject 可直接使用智能指针
GcDbObjectPointer<GcDbLine> pLine(objId);
pLine->doSomething();  // 安全,计数器自动管理

// 而 GcRxObject 不同:
// ❌ 直接使用智能指针可能导致内存泄漏
GcRxObjectPointer<GcRxSomething> pObj;  // 不推荐!

// ✅ 必须使用 createObject 创建才能使用智能指针
GcRxObjectImpl* pImpl = GcRxObjectImpl::createObject();
// 现在可以安全使用智能指针

4.3 重要警告

GcDbObject 对象,因为 Impl 自带计数器,所以就可以直接使用智能指针。GcRxObject 不行,只有创建对象使用 GcRxObjectImpl::createObject() 才能使用智能指针,否则可能内存泄漏。

这是 GRX 开发中最容易踩的坑!


五、GRX 的 GcSmartPtr 的设计和使用

5.1 GcSmartPtr 的设计哲学

GRX 提供了比 ObjectARX 更现代化的智能指针系统 GcSmartPtr,其设计目标:

  1. 线程安全:内部使用原子引用计数
  2. 零开销抽象:编译期优化,运行时无额外开销
  3. 兼容 STL:可与标准库容器配合使用
  4. 自动生命周期:RAII 完全自动化

5.2 与 ObjectARX 智能指针对比

特性 ObjectARX AcDbObjectPointer GRX GcSmartPtr
引用计数 外部管理(配合 DB) 内置原子计数器
线程安全 否(单线程假设) 是(原子操作)
语法 模板类 更像 std::shared_ptr
性能 有开销检查 零开销(优化后)
使用范围 仅 AcDbObject 更广(包括 GcRxObject)

5.3 使用示例

基本用法

// 创建对象并使用智能指针管理
GcSmartPtr<GcDbLine> pLine = new GcDbLine(startPt, endPt);

// 添加到数据库
GcDbObjectId objId;
pBlockTableRecord->appendAcDbEntity(objId, pLine.get());

// 无需手动 close,智能指针自动处理
// 但要注意:添加到 DB 后所有权转移,智能指针应保持引用

与标准库容器配合

#include <vector>

std::vector<GcSmartPtr<GcDbEntity>> entities;

// 安全存储
GcSmartPtr<GcDbLine> pLine = new GcDbLine(...);
entities.push_back(pLine);

// 自动释放,无需手动管理
entities.clear();  // 所有对象自动引用计数减1

与 GcRxObject 的配合(重要!)

// ❌ 错误:直接使用 new GcRxSomething
GcSmartPtr<GcRxSomething> pRx = new GcRxSomething();  // 危险!

// ✅ 正确:使用 createObject
GcRxObjectImpl* pImpl = GcRxObjectImpl::createObject();
GcSmartPtr<GcRxSomething> pRx(pImpl);
// 现在可以安全使用

5.4 最佳实践总结

GRX 开发 checklist

对象类型 创建方式 智能指针 注意事项
GcDbObject new GcDbObjectPointer 直接可用
GcDbObject new GcSmartPtr 添加到 DB 后所有权转移
GcRxObject new ❌ 禁止 必须使用 createObject
GcRxObject GcRxObjectImpl::createObject() GcSmartPtr 安全使用

六、总结与建议

6.1 ObjectARX 要点

  1. AcRxObject 是元对象系统,解决跨 DLL 类型识别和动态创建
  2. AcDbObject 使用数据库托管模式,close() 而非 delete
  3. Smart Pointer 使用 AcDbObjectPointerAcDbSmartObjectPointer
  4. 协议扩展 queryX() 比继承更灵活

6.2 GRX 要点

  1. 完全兼容 ObjectARX API,但内部实现不同
  2. GcDbObject 内置计数器,可直接使用智能指针
  3. GcRxObject 必须使用 createObject 才能配合智能指针
  4. GcSmartPtr 更现代化,支持 STL 和线程安全

6.3 迁移建议

如果从 ObjectARX 迁移到 GRX:

// ObjectARX 代码
AcDbObjectPointer<AcDbLine> pLine(objId, AcDb::kForRead);

// GRX 等效代码(更简洁)
GcDbObjectPointer<GcDbLine> pLine(objId);  // 模式自动推断

// 或使用 GcSmartPtr(推荐新代码)
GcSmartPtr<GcDbLine> pLine = GcDbLine::create(...);