近期,由于项目的需要做一套撤销恢复机制,特地研究了一下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文件名和对象对应的句柄了,因为,句柄只是在一个数据库中唯一,不同的数据库中,句柄是会重复的。
如果你知道一个句柄,那么你可以通过来得到对应对句的objectId。为什么接口在AcDbDatabase上,这个很好理解吧,因为句柄才是数据库唯一的啊。
如果你知道对象的objectId想得到对象对应的句柄,你可以打开这个对象并且使用接口来获取,因为,objectId保存的内存地址对应的AcDbStub对象保存着对应的数据库对象的指针,而句柄是这个数据库对象上一个属性而已,所以 很容易就得到了。
由上面这些内容,可以想像得到,为数据库对象分配ID的机制就是使用了在同一会话中,不同节点加载的内存地址肯定是不同的这一原理和小技巧来实现的,而为数据库对象分配句柄的则是DWG数据库中实现的一套机制,很容易,句柄不就是一个64位值嘛,加一个节点累加一次就是了。 为了要灵活的管理内存,而使用了代理对象的这一个方案(AcDbStub对象)。