Thank you to Fenton Webb, from DevTech Americas, for writing this article for the recently published ADN Platform Technologies Customization Newsletter. This article also talks about the new AcDbSmartObjectPointer class referenced in this overview of the new APIs in AutoCAD 2009.
Those of us who regular create ObjectARX code to manipulate the AutoCAD drawing database are fully aware of the mechanism for opening an object for read (to simply access data held inside it) or write (to update it with new data). Oh and I almost forgot - followed by a call to close() when you are done.
But here lies a very common problem illustrated by that last sentence; the problems start when you accidentally forget to close an object once you are finished with it. AutoCAD follows a strict set of rules which allows the checking-in/out of data inside of AutoCAD and these rules must be adhered to. If not, then AutoCAD will abort in order to do its best to save the previous valid state of the database.
“You must be very careful to close your objects when you are finished with them”. It’s very easy for me to say that, but even I, the person shaking his finger saying those infamous words will fail to remember my own advice time and time again, this is why using ObjectARX SmartPointers is a MUST.
So let’s look at this thing in ObjectARX called SmartPointers.
What are they? First take a look at the MSDN article on “Template Classes” as this explains the basic concept. Leading on from that article, and now in my own words, ObjectARX SmartPointers are C++ template classes which wrap an underlying AutoCAD AcDbObject derived class pointer, and simply provides automatic closure of that pointer, if valid, on destruction of the ObjectARX SmartPointer class (so the end of a function or closing brace “}”).
A question that often arises is on the usage of this class, in particular the way to access the member functions. The template class itself has been implemented so that if you reference a member function with the dot “.” operator
line.openStatus()
then, you reference the ObjectARX SmartPointer specific functions. If you reference a member function with the arrow “->” operator
line->setStartPoint()
Then, because the arrow operator has been overridden to return the underlying AcDbObject pointer, you simply reference the underlying AcDbObject derived class, in this case the AcDbLine::setStartPoint().
So how do we use them then…? Let’s start by showing old ObjectARX code which adds an AcDbLine to the Current Space using open and close.
// create a new line
AcDbLine *line = new AcDbLine();
// set the properties for it
line->setStartPoint(AcGePoint3d(10,10,0));
line->setEndPoint(AcGePoint3d(20,30,0));
// now add it to the current space
AcDbBlockTableRecord *curSpace = NULL;
// open the current space for write
Acad::ErrorStatus es =
acdbOpenObject(
(AcDbBlockTableRecord *&)curSpace,
curDoc()->database()->currentSpaceId(),
AcDb::kForWrite
);
// if ok
if (es == Acad::eOk)
{
// add it to the space
es = curSpace->appendAcDbEntity(line);
// check that everything was ok
if (es != Acad::eOk)
{
delete line;
return;
}
// now close everything
line->close();
curSpace->close();
}
It's the 2 close statements at the end which are, first of all, very easy to forget to put in, but also notice they return just before which indicates a very rare failure, but just as importantly (and erroneously) bypasses the close of curSpace.
This is where ObjectARX SmartPointers not only provide automatic closure and cleanup but also peace of mind…
Let’s take a look at the same code, but this time using ObjectARX SmartPointers.
// create a new line
AcDbObjectPointer<AcDbLine> line;
line.create();
// set the properties for it
line->setStartPoint(AcGePoint3d(10,10,0));
line->setEndPoint(AcGePoint3d(20,30,0));
// now add it to the current space
AcDbBlockTableRecordPointer
curSpace(
curDoc()->database()->currentSpaceId(),
AcDb::kForWrite
);
// if ok
if (curSpace.openStatus() == Acad::eOk)
{
Acad::ErrorStatus es =
curSpace->appendAcDbEntity(line);
// check that everything was ok
if (es != Acad::eOk)
{
// no need for a delete as the smartpointer does this for us
return;
}
}
// everything will be closed automatically for us
Not only is this ObjectARX code "close" safe, it is also memory leak-safe. Also, look how much tidier it is. Much more friendly in my opinion!
Here’s some more SmartPointer code which selects an Entity on screen and opens it for read, just as an example.
ads_name ename;
ads_point pt;
// pick an entity to check
int res = acedEntSel (_T("\nPick a Line : "), ename, pt);
// if the user didn't cancel
if (res == RTNORM)
{
AcDbObjectId objId;
// convert the ename to an object id
acdbGetObjectId (objId, ename);
// open the entity for read
AcDbObjectPointer<AcDbLine>ent (objId, AcDb::kForRead);
// if ok
if (ent.openStatus () == Acad::eOk)
{
AcGePoint3d startPnt;
ent->startPoint(startPnt);
// do something
}
}
But what if you have reams and reams of existing code using old-style open and close, and you want to migrate to ObjectARX Smart Pointers with the least amount of effort? Well, we’ve tried to make it easy for you. Since ObjectARX 2007, in dbobjptr.h simply uncomment the #define DBOBJPTR_EXPOSE_PTR_REF and now life should be easy! (Well, with one exception - see **NOTE below).
Here’s the converted version of the original code we used at the beginning, converting to use ObjectARX SmartPointers couldn’t be easier (I’ve highlighted the changes in bold).
// create a new line
AcDbObjectPointer<AcDbLine> line = new AcDbLine();
// set the properties for it
line->setStartPoint(AcGePoint3d(10,10,0));
line->setEndPoint(AcGePoint3d(20,30,0));
// now add it to the current space
AcDbBlockTableRecordPointer curSpace = NULL;
// open the current space for write
Acad::ErrorStatus es =
acdbOpenObject(
(AcDbBlockTableRecord *&)curSpace,
curDoc()->database()->currentSpaceId(),
AcDb::kForWrite
);
// if ok
if (es == Acad::eOk)
{
// add it to the space
es = curSpace->appendAcDbEntity(line);
// check that everything was ok
if (es != Acad::eOk)
{
delete line;
return;
}
// now close everything
line->close();
curSpace->close();
}
Notice that I didn’t bother to remove the two close() calls at the end, there’s no need. If you close them by hand, or forget, it’s all good with ObjectARX SmartPointers.
**NOTE: So, in order to get the acdbOpenObject to accept the same code as before, in dbobjptr.h, at line 467 (ObjectARX 2009 SDK), there is an assert which needs to be omitted; either #define NDEBUG or I recommend that you simply change the assert to be enclosed by the #ifndef DBOBJPTR_EXPOSE_PTR_REF
AcDbObjectPointerBase<T_OBJECT>::object(){
#ifndef DBOBJPTR_EXPOSE_PTR_REF
assert(m_status == Acad::eOk);
#endif // DBOBJPTR_EXPOSE_PTR_REF
Last but not the least is the new AcDbSmartObjectPointer template class in ObjectARX 2009, defined in the header file dbobjptr2.h.
This new template class works in the same way as AcDbObjectPointer template class except that it works by NOT opening an object at all if its open state is already what was requested, or even closing an object multiple times before opening in the desired manner. It merely hands you the already opened object pointer for your use. This means that it is much more efficient and also much more powerful in its usage. It also treats kForNotify and kForRead in the same manner, which is effectively kForRead.
One feature of this new SmartPointer class that I’d like to talk about is the ability to multiply open an object for write, from different places, at the same time, a bit like a Transaction can – this is extremely powerful when you think about it.
At the same time though, I find thinking about the power that this can provide can start generating some other complex thoughts and scenarios that maybe we should be cautious of; the bottom line is that you should be very careful about multiply opening an object for write no matter how good the class that controls it.
An example of where this type of functionality really might be useful to us developers is in say an Object Reactor callback. Quite often you might want to modify the current object’s state but of course you can’t because it is already open for notify. Using this new SmartPointer class it makes it possible to modify the object as you see fit in this context, but be careful to handle the recursive object modified notifications that will be fired by doing this.
All in all a very exciting new addition to the ObjectARX API, make sure you check it out.