Calling Dispose() on AutoCAD objects

This is a follow-on to this previous post, prompted by a thread over on The Swamp discussing a recent blog post by Fenton Webb on the AutoCAD DevBlog.

Fenton's assertion is that you really need to call Dispose() on all AutoCAD objects that you create yourself, unless they are managed by AutoCAD's transaction system (i.e. you've passed responsibility across to AutoCAD by calling Transaction.AddNewlyCreatedDBObject()). Which means that while you don't need to call Dispose() on objects such as the AutoCAD Editor or the active Document (and you really shouldn't), you really should call Dispose() on various objects you've been used to letting the .NET garbage collector (GC) dispose of, such as those belonging to the Autodesk.AutoCAD.Geometry namespace.

In my previous post on this topic, I said:

"The first category of temporary objects, such as Geometry.Line [sic – I obviously meant Geometry.Line3d], are safe to be disposed of either "manually" (by your own code) or "automatically" (by the .NET garbage collector)."

As Fenton says, the word "safe" isn't strictly true: while in practice it's safe for the majority of classes belonging to the Autodesk.AutoCAD.Geometry namespace, in theory some of them could cause problems if finalized by a thread other than the UI thread. This happens when a class implementation relies on some kind of shared resource – or modifies shared state in some way – in which case it's quite likely to release the resource/modify the state when destroyed. And if this should happen from a thread other than the main thread (and the GC runs on a background thread by default in .NET) then bad things happen. Which usually means AutoCAD crashes.

Rather than tell developers "you really need to go and modify all the AutoCAD .NET code you've written over the last 8-9 years to call Dispose() every time you create such an object", I thought it would be more helpful to analyse the risks a little more closely.

Before going into the analysis, I would say that at this stage it should be considered best practice to dispose of all temporary AutoCAD .NET objects – however simple they may appear – explicitly from your code. This can be done via the Dispose() method or via a using() block, of course. It certainly would continue to seem "safe" to leave certain objects to the GC, but then you never really know when and where complexity might inadvertently be added.

I had always felt that Autodesk.AutoCAD.Geometry was pretty safe, in this regard(hence my post of 4 years ago), but it turns out the namespace contains a few corner cases, even now, that could easily cause you problems.

To analyse the extent of the problem, I started by grepping our internal source for AcGe classes with destructors. I found 18 such internal classes, of which 11 were exposed via the ObjectARX layer (i.e. there were AcGe equivalents for them).

Here are those classes. Not all of their destructors actually do anything very complicated – most of the classes only maintain local object state, for instance, which means they can effectively (today) have their managed counterparts' finalization left to the GC (for those that have them – not all of these classes are exposed via .NET, either) – but there are some that are altogether riskier.

  • AcGeCurveBoundary
  • AcGeCurveSurfInt
  • AcGeSurfSurfInt
  • AcGeNurbCurve3d
  • AcGeOffsetCurve3d
  • AcGePointOnCurve3d
  • AcGePointOnSurface
  • AcGeTrimmedSurface
  • AcGeExternalCurve2d
  • AcGeExternalCurve3d
  • AcGeExternalSurface
  • AcGeExternalBoundedSurface

Now I haven't stepped through each of these classes' destructors in the debugger to see which are genuinely problematic, but I can say that particular care needs to be taken with the ones in bold. They essentially form a connection between the (otherwise fairly self-contained) geometry library and the BRep API inside AutoCAD: they exist to maintain links to externally-defined geometry, such as that contained within the ASM (Autodesk Shape Manager) sub-system.

And – sure enough – Stephen Preston tells me that the ADN team (and AutoCAD Engineering) have had to track down issues related specifically to not calling Dispose() on objects created by AutoCAD's BRep API, in the past.

So, to summarise… I don't suggest people necessarily go back through and systematically call Dispose() on every geometry object they've ever created within their AutoCAD .NET code, but it's certainly worth paying attention to this, moving forwards, as well as keeping in mind the problematic areas (such as BRep) should unexplained crash reports start to come in from users.

4 responses to “Calling Dispose() on AutoCAD objects”

  1. I think an example or two with comments would be *REALLY* helpful.

  2. Sure thing - will put something together...

    Kean

  3. Kean,

    I went through a great deal of my code yesterday updating it with the recommendations both here and in your previous post. I noticed some patterns which may help you set up your samples (or not), but I thought I'd share them anyway.

    Within a transaction where something is added to the database, some new objects (Xrecords, dictionaries) can be relatively simple and might only need a Using block. Conversely, complex objects (dimension styles, block definitions with attributes) have many properties and resulting code with potential points of failure. How would you deal with that in a Try...Catch block + Using block to handle exceptions at the object level and the transaction level?

    A second thing that I found was casting Entities to specific object types, for example iterating through block definitions looking for a line, and casting that Entity to a Line object. Do I need to dispose of the Line because I didn't use line = transaction.GetObject(...), but rather line = TryCast(entity, Line)? In other words, is the new object a "transaction-managed" object, or do I need to dispose of it, or is it not a new object to the database and I don't need to worry about it?

    I hope that helps you target some things I'm still not perfectly clear on. I appreciate your consideration.

  4. Danny,

    For your first point - if I've understood it correctly - you shouldn't need to worry: as long as the owning object is added to a transaction (or opened by it) then it will be managed by the transaction and doesn't need to be disposed of in your code. Of course if you have associated objects - such as block definitions - they should be added to the transaction in their own right.

    And the second point should also not be an issue: the entity was opened by the transaction. When you're casting from one object type to another, you're really just maintaining an identical object reference (with the same underlying unmanaged pointer) but of a different type. So as long as one of the references is disposed (and in this case it is being done so by the transaction, but the same would be true if you were dealing with objects outside the transaction system), then all is well.

    Hopefully these comments help clarify these points - I'll fold them into today's post, as they're very valid questions.

    Regards,

    Kean

Leave a Reply to Danny P. Cancel reply

Your email address will not be published. Required fields are marked *