AcDbObjectId、AcDbHandle与AcDbStub之间的关系 阿门原创

 

阿门原创 http://hi.baidu.com/lihao102     欢迎转载

 

近期,由于项目的需要做一套撤销恢复机制,特地研究了一下AutoCAD(下文称为ACAD)的撤销恢复机制,ACAD的撤销和恢复是无限次的,也是就说,只要是同在一个会话(打开到关闭的一个过程)中的操作你都可以撤销到原来的状态或者是重做刚才被撤销的状态。而要实现整个一套机制的基础就包涵了所要说的三个主人公了。这篇文件就用来说明这三者之间的关系。

 

从ACAD R13 Release版本起,为了处理数据库对象在内存生存周期而设计了一套方案。这个方案为每一个对象都引用了两个部分的内容。第一部分,就是数据库对象本身,它是能够常驻内存并且,如果内存需要释放的话它也能被换页到磁盘上,第二部分是一个叫做桩(stub)的对象(AcDbStub类),这个对象是常驻内存的,并且担当着数据库对象入口的职责,当然,AcDbStub只是一个很小的对象。这么一来,也就是说,如果ACAD系统需要回收内存,第一部分是可以被回收的,但第二部分是不允许回收的(当前系统动态数据中与原数据库对象的唯一协议),这个可以理解成一种代理节点的机制。

 

当一个对象或者是实体加入数据库时,系统将自动为其建立一个AcDbStub对象,并将其指向到这个节点,然后再加入AcDbStub和被加入的对象或实体到数据库。而这个AcDbStub对象的内存地址就被当作是这个对象或实体的ads_name和AcDbObjectId了。同样的机制也应用在数据库从磁盘读入内存的时候。所以这也就是为什么ads_name与AcDbObjectId的能够提供转换接口的原因了,因为它们本来就是一种东东。我们还可以看到更深一点,就是当一个节点从数据库加载到内存时,它所对应的AcDbStub对象肯定是先于它本身来加载的。这样才能使这一套机制正确的执行下去。(AcDbStub对象也是需要编档的哦,当然这一部分工作是系统本身来做)。

 

         当一个数据库对象被打开时,ObjectARX应用程序使用数据库对象的objectId传入open接口,然后返回真正的数据库对象的指针。事实是怎么进行的呢?那就是这个objectId就是这个数据库对象所对应的AcDbStub对象在内存中的地址(这个可以证明,这个调用开始前,这个AcDbStub对象肯定已经被加载到内存),然后通过这个桩就可以得到真正需要得到的数据库对象的指针。如果此时这个数据库对象已经被卸载掉(从内存中移除),系统机制会把这个对象重新加载然后AcDbStub指向这个对象的新地址。

 

         因此,一个AcDbObjectId对象是一个包括着真正数据库对象所对应的桩的地址的容器。并且,它是一个非常重要的对象,因为它是一个会话过程中数据库节点的分配唯一的标识的机制(这样处理确保其在一个会话过程中不会重复,你有没有见过,同一个内存地址包括两个对象啊?)。

 

         很多情况下,有可能用户定义了一些自定义对象或者是自定义实体,并且在其中需要编档一些其它节点的objectId,这是允许的(如果你写过自定义对象和自定义实体,你就知道用得很广泛),因为,在编档的时候,这套机制会自动把objectId转换成对应数据库对象的句柄(AcDbHandle),所以,它们通常能够在不同地会话过程中识别为同一个对象或实体,因为objectId是会话时期唯一的(内存地址嘛),而句柄是数据库唯一的,所以,只要确实转换正确,就能够跨会话来标识为同一个数据库对象,这样,下一个会话再使用的时候,节点之间的关系是正确保存了也能够正确给读进来的。当编档对象的objectId的时候,要明确的编档成为正确的关系类型(AcDbHardPointerId, AcDbSoftOwnershipId等等),相信写过自定义实体的人都编过,呵呵,只有这个ObjectARX内部机制才能正确的将它们之前的关系正确的转向成句柄,并编档好。

 

         如果你需要把一个对象正确的保存到一个扩展文件中(非dwg或dxf文件),这时你就不能使用objectId了(原因很简单,不能转换了啊,对象只有dwg和dxf的编档接口(可以看看AcDbObject类的接口)),因为,objectId的值在不同的会话中是不同的。可以使用的替代方案是,你必须保存这个数据库对象对应的句柄,跨会话就只能用数据库唯一的标识——句柄。如果你当前工作环境下有多个数据库文件(dwg文件),那么为了达到唯一标识一个对象的话,你必须保存好dwg文件名和对象对应的句柄了,因为,句柄只是在一个数据库中唯一,不同的数据库中,句柄是会重复的。

 

         如果你知道一个句柄,那么你可以通过AcDbDatabase::getAcDbObjectId()来得到对应对句的objectId。为什么接口在AcDbDatabase上,这个很好理解吧,因为句柄才是数据库唯一的啊。

 

         如果你知道对象的objectId想得到对象对应的句柄,你可以打开这个对象并且使用AcDbObject::getAcDbHandle()接口来获取,因为,objectId保存的内存地址对应的AcDbStub对象保存着对应的数据库对象的指针,而句柄是这个数据库对象上一个属性而已,所以 很容易就得到了。

 

 

         由上面这些内容,可以想像得到,为数据库对象分配ID的机制就是使用了在同一会话中,不同节点加载的内存地址肯定是不同的这一原理和小技巧来实现的,而为数据库对象分配句柄的则是DWG数据库中实现的一套机制,很容易,句柄不就是一个64位值嘛,加一个节点累加一次就是了。 为了要灵活的管理内存,而使用了代理对象的这一个方案(AcDbStub对象)。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ObjectARX 中,可以通过将自定义结构体序列化为字符串,然后将其保存在实体的扩展数据中来实现保存自定义结构体。 以下是实现此功能的一般步骤: 1. 定义自定义结构体,并在其中添加需要存储的字段。 ``` struct MyData { double value1; int value2; wchar_t name[50]; }; ``` 2. 将自定义结构体序列化为字符串。可以使用 STL 中的 stringstream 来实现。 ``` MyData data; // 设置 data 的值 std::stringstream ss; ss << data.value1 << "," << data.value2 << "," << data.name; std::string str = ss.str(); ``` 3. 将序列化后的字符串保存到实体的扩展数据中。可以使用 AcDbObjectId::setXData() 方法来设置扩展数据。 ``` AcDbObjectId entityId; // 实体的 Object ID AcDbEntity *pEntity = nullptr; acdbOpenAcDbEntity(pEntity, entityId, AcDb::kForWrite); if (pEntity) { resbuf *pResBuf = acutBuildList( AcDb::kDxfRegAppName, "MyApp", AcDb::kDxfXdAsciiString, str.c_str(), 0); pEntity->setXData(pResBuf); acutRelRb(pResBuf); pEntity->close(); } ``` 4. 读取实体的扩展数据并反序列化为自定义结构体。可以使用 AcDbObjectId::getXData() 方法来读取扩展数据。 ``` AcDbObjectId entityId; // 实体的 Object ID AcDbEntity *pEntity = nullptr; acdbOpenAcDbEntity(pEntity, entityId, AcDb::kForRead); if (pEntity) { resbuf *pResBuf = pEntity->getXData("MyApp"); if (pResBuf) { std::wstring str; for (resbuf *pRes = pResBuf; pRes; pRes = pRes->rbnext) { if (pRes->restype == AcDb::kDxfXdAsciiString) { str = pRes->resval.rstring; break; } } acutRelRb(pResBuf); std::wstringstream ss(str); MyData data; ss >> data.value1; ss.ignore(); ss >> data.value2; ss.ignore(); ss.getline(data.name, 50); } pEntity->close(); } ``` 注意,实体的扩展数据可用于存储任意类型的数据,但建议使用自定义应用程序名(例如 "MyApp")来标识存储的数据类型,以避免与其他应用程序的扩展数据冲突。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值