Kean Walmsley

July 2009

Sun Mon Tue Wed Thu Fri Sat
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31  

Twitter Updates

    follow me on Twitter



    « Unpublished AutoCAD 2009 APIs to improve application, OS and system performance | Main | Extracting XML data from drawings using a new .NET API in AutoCAD 2009 »

    April 03, 2008

    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.

    TrackBack

    TrackBack URL for this entry:
    http://www.typepad.com/services/trackback/6a00d83452464869e200e5519636ea8833

    Listed below are links to weblogs that reference Automatic closing of AutoCAD objects with ObjectARX SmartPointers:

    Comments

    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.

    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.

    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.

    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

    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.

    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

    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

    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search