Automatic closing of AutoCAD objects with ObjectARX SmartPointers
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.

Subscribe via RSS
Great Kean,
One concern.
The smart pointer close the object pointer as soon as its scope is finished. This behavior is the same as the previous smart pointer class. One problem that still concerns me is when you need to pass a pointer to other functions. In this case as soon as it leaves the function scope it will be closed and the pointer will be invalid outisde the function scope.
I think we will need to keep these smart pointers as class members to allow it to safely travel from class functions to funcions. If you need to pass this pointer from one class to another it will need to be a global variable.
Is there a "smarter" way to handle these cross scope smart pointers to a safe roundtrip ?
Best Regards,
Fernando.
Posted by: Fernando Malard | April 03, 2008 at 09:38 PM
Fernando,
You might be over-thinking the problem of scope. I use smart pointers all the time and I can pass the object pointer down to a function no problem. I call the function from within the scope that created the smart pointer and all is well. Going into a new function does not end the scope of defined variables on the stack. The new items are added to the stack and popped off when the function returns.
Posted by: Mike Gill | April 03, 2008 at 10:31 PM
Sorry, I did not explain this clear enough.
The situation is when you need a function which will open an object for you and return this pointer through its return value. Something like this:
AcDbEntity* getSmallestHeightPanel()
{
AcDbEntity* pEnt = NULL;
...
acdbOpenObject(idEnt,pEnt,AcDb::kForRead);
...
return pEnt;
}
If you use a smart pointer in this case it will be out of scope when returned by your function.
In this case you will need to pass the smart pointer variable by reference so its scope is outside the function's scope.
This type of function syntax (where the pointer is the returned value) is very convenient in some cases.
Hope you get the idea now.
Regards,
Fernando Malard.
Posted by: Fernando Malard | April 03, 2008 at 10:50 PM
Fernando,
I would suggest that if you wish to return a pointer from a function, then you wouldn't want to use a SmartPointer to open that object in the first place. SmartPointers are a convenience to automatically close objects - something you want to avoid here.
That said, you might want to use a new SmartPointer to receive the return value of the function:
AcDbSmartObjectPointer panel(getSmallestHeightPanel()->objectId(), AcDb::kForRead);
This would not actually reopen the panel entity, assuming it was already open for read.
Regards,
Kean
Posted by: Kean | April 04, 2008 at 10:17 AM
I see.
So the smart pointer actually checks for the current open state instead of using some local variable to control the opening status.
Interesting. This way is easier to mix with the traditional open/close approach.
This improvement in 2009 is only related to the template class or are there some Core modifications to allow this? Can we use this template back to ObjectARX 2007 and 2008?
Regards.
Posted by: Fernando Malard | April 04, 2008 at 01:16 PM
If you check dbobjptr2.h in ObjectARX 2009, you'll see a few internal-use function declarations, accessAcDbObjectForRead() and accessAcDbObjectForWrite(), which are exports from acdb17.dll.
These exports appear to be there for 2008 (I didn't check any further back than that), but I wouldn't recommend attempting to use this header (and therefore these classes) in versions before 2009: object open/close is a risky area to play around with, and there may well have been internal changes made when getting this API ready for external consumption.
Regards,
Kean
Posted by: Kean | April 04, 2008 at 01:48 PM
Hi Fernando
a very interesting Use case... So using the DBOBJPTR_EXPOSE_PTR_REF define and with the changes I advised you make in dbobjptr.h your scenario is neatly dealt with. Here is what I mean...
So without any changes to your existing routine getSmallestHeightPanel(), simply change the calling routine from (and I'm guessing)
AcDbEntity *ent = getSmallestHeightPanel();
to look like this...
AcDbObjectPointer ent = getSmallestHeightPanel();
Now the returned entity is closed automatically for all return conditions, and you can sleep well at night! :-)
Remember that you don't need to change your code to remove the ->close() calls because they are still valid.
I hope this helps.
Cheers
Fenton
Posted by: Fenton Webb | April 04, 2008 at 07:17 PM