Author Topic: How to get Entities in a Block.  (Read 18488 times)

0 Members and 1 Guest are viewing this topic.

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #15 on: January 12, 2012, 08:45:42 AM »
example of how the garbage collector works with DBObjects... when do you see the message  :-)

Code: [Select]
#region
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
#endregion

[assembly: CommandClass(typeof(ExecMethod.Commands))]
namespace ExecMethod
{
  public class leak : DBPoint
  {
    protected override void DeleteUnmanagedObject()
    {
      System.Windows.Forms.MessageBox.Show("HI : )");
      base.DeleteUnmanagedObject();
    }
  }
  public class Commands
  {
    [CommandMethod("leak")]
    public static void leak()
    {
      leak spring = new leak();

    }
  }
}

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #16 on: January 12, 2012, 08:51:49 AM »
Threading has nothing to do with it. the issue is 'when' to destroy a native pointer within a wrapper, in a garbage collected framework that does not have scoping? A, you don't, you suppress the finalizer and hand it off to the developer then put words like " it's important to dispose the object (perhaps explicitly)" in your documentation.  but it is what it is, if you create it and don't hand it off to an owner, dispose it, Easy Peasy Lemon Squeezy

Well, threading has everything to do with why you must determinstically call Dispose() on DBObjects that are not database-resident.

But, in a managed environment with garbage collection, it shouldn't be necessary to call Dispose() on anything, unless there is something special that must occur when the object is no longer used (an example would be a class that manages an open file, where calling Dispose() would cause the file to be closed, allowing others to access it).

Unfortunately, we're stuck with a cooperative fiber-mode application that has many APIs that aren't thread-safe, which means they can only be called on one of the application-managed threads/fibers, and that is the root cause of the utterly-ridiculous Dispose() requirement that vastly-complicates development of managed AutoCAD solutions.

The need to call dispose in acad is by design, nothing to do with what thread the GC runs on. Study the DisposableWrapper class  :-)

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #17 on: January 12, 2012, 09:03:58 AM »
Take the following class as an example. When should the GC call the finalaizer? How does the GC know that Acad does not need the pointer? it doesn't, either you hand the task to an owner,  such as the transaction manager,  or you, the developer must call dispose.. by design    :-)

Code - C++: [Select]
  1.        ref class LineWrapper
  2.         {
  3.             AcDbLine *pline;
  4.             LineWrapper()
  5.             {
  6.                 pline = new AcDbLine();
  7.             }
  8.             ~LineWrapper()
  9.             {
  10.                 GC::SuppressFinalize(this);
  11.                 this->!LineWrapper();
  12.             }
  13.             !LineWrapper()
  14.             {
  15.                 delete pline;
  16.             }
  17.         };
  18.  

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #18 on: January 12, 2012, 09:11:10 AM »
@ tik, sorry to hijack your thread..

Just an FYI BlockRecords own the geometry,  BlockReferences can own attributes.  :-)

TheMaster

  • Guest
Re: How to get Entities in a Block.
« Reply #19 on: January 12, 2012, 09:38:27 AM »

Take the following class as an example. When should the GC call the finalaizer?


When there are no longer any reachable references to the object? :-D

In managed code, when an object can no longer be reached through code (e.g., there are no longer any usable references to it) it becomes eligible for collection - by design.  That's how managed code was intended to work.

The "Dispose()" method is really just given you a way to tell an object that you are done using it, in spite of the fact that there may still be reachable references to it.

What an object does in response to being told that, is entirely up to the object.

You might note that many managed objects in the API are all based on DisposableWrapper, but in many cases, it was done that way largely out of convenience, and for many of those managed wrapper types it is entirely unnecessary to call Dispose() on them. If you don't, they get collected and their destructors get called. Calling a destructor from the GCs thread is not supposed to be a problem, and in many cases it is not.

So, we can't say that the need to call Dispose() is by-design, because in a managed environment, what calling Dispose() does will inevitably be done anyways, when an object is collected, and that is the whole intent of a 'managed' execution environment  - to remove the burden of managing object life from the programmer and making the runtime responsible for it.


Quote

How does the GC know that Acad does not need the pointer? it doesn't,


Sure it does :grin:.  The GC knows that because the programmer allowed the wrapper to become unreachable from any code. That is how the GC determines that a managed object is no longer needed and can be collected.

Further, it isn't Acad that needs the pointer, it is the programmer that has the managed wrapper for the pointed-to object.

Quote

either you hand the task to an owner,  such as the transaction manager,  or you, the developer must call dispose.. by design    :-)


No, sorry to disagree but this is not by design. In fact, I have talked about it at length with the architect of AutoCAD's managed API and he has lamented about the fact that it is necessary for the programmer to determinstically Dispose() managed  wrappers for AcDbObjects that aren't database-resident. It shouldn't be necessary and for that matter, it wouldn't be necessary if Microsoft had done what it said it was going to do (allow a CLR host application to create and manage the thread which the GC runs on).  That was one of the features that was supposed to go into .NET 2.0, but was cut at the last minute, mainly because Microsoft decided to abandon the cooperative fiber-mode threading model in SQL Server, making that CLR feature unnecessary for their own use therein.

http://www.theswamp.org/index.php?topic=16375.0

Had Microsoft added that feature to the CLR like they proposed doing, then today, the GC would be running on an AutoCAD thread, and none of this utter nonsense would be necessary.
« Last Edit: January 12, 2012, 09:51:04 AM by TheMaster »

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #20 on: January 12, 2012, 10:19:06 AM »
did you run this code? when does the collector fire?
www.theswamp.org/index.php?topic=40630.msg459240#msg459240


additionally, you can derive from disposable wrapper and examine the behavior, you just might be surprised  : )
« Last Edit: January 12, 2012, 10:22:47 AM by eBeerHandler »

TheMaster

  • Guest
Re: How to get Entities in a Block.
« Reply #21 on: January 12, 2012, 10:25:05 AM »
did you run this code? when does the collector fire?
www.theswamp.org/index.php?topic=40630.msg459240#msg459240


additionally, you can derive from disposable wrapper and examine the behavior, you just might be surprised  : )

No I didn't run it.  When do you see the message?


It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #22 on: January 12, 2012, 10:26:29 AM »
you tell me  :lol:

tik

  • Guest
Re: How to get Entities in a Block.
« Reply #23 on: January 12, 2012, 10:28:53 AM »
Guys,

I am sitting here and reading through the posts and learning a lot. Thanks for sharing your knowledge with every one.

Jeff,

Thanks for the code sample it worked like a charm but I have one issue, when i get the entities from the BlockTableRecord there is no XData in those entities, does this goes back to that cloning issue again or am I doing something wrong.


TheMaster

  • Guest
Re: How to get Entities in a Block.
« Reply #24 on: January 12, 2012, 10:37:56 AM »
you tell me  :lol:

Can't right now, I'm on my netbook, far from my desktop :grin:

Just tell us :lol:

I shouldn't see any anything until the next GC.

And in case you didn't know this, even a simple call to acutPrintF() will fail in that same context (with no exeception or any kind of error message).

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #25 on: January 12, 2012, 10:41:08 AM »
 I will wait for you, and your explanation as to 'why' DisposableWrapper behaves the way it does.     

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #26 on: January 14, 2012, 06:19:27 AM »
you tell me  :lol:

Can't right now, I'm on my netbook, far from my desktop :grin:

Just tell us :lol:

I shouldn't see any anything until the next GC.

And in case you didn't know this, even a simple call to acutPrintF() will fail in that same context (with no exeception or any kind of error message).

Did you quit playing?

Jeff H

  • Needs a day job
  • Posts: 6150
Re: How to get Entities in a Block.
« Reply #27 on: February 04, 2012, 11:07:54 PM »
What about this?
 
A little change to your code Daniel.
Code: [Select]
public class leak : Line
    {
        protected override void DeleteUnmanagedObject()
        {
            Application.ShowAlertDialog("DeleteUnmanagedObject");
            HostApplicationServices.WorkingDatabase.Clayer = HostApplicationServices.WorkingDatabase.LayerZero;
            base.DeleteUnmanagedObject();
        }
        protected override void Dispose(bool A_1)
        {
            Application.ShowAlertDialog("Dispose");
            base.Dispose(A_1);
            HostApplicationServices.WorkingDatabase.Clayer = HostApplicationServices.WorkingDatabase.LayerZero;
           
        }
    }
    public class Commands
    {
        [CommandMethod("leak")]
        public static void leak()
        {
            leak spring = new leak();
            spring.LayerId = HostApplicationServices.WorkingDatabase.Clayer;
        }
And it goes Boom!!!
 
Now open up acad.exe.config and add <gcConcurrent> Element
Code: [Select]
   <runtime>       
 <generatePublisherEvidence enabled="false"/>
     <gcConcurrent enabled="false"/>
   </runtime>

Start Acad back up run command again and no boom
 
 
A little more info
How to: Disable Concurrent Garbage Collection
Also the
<gcServer> Element
 
 
 
 

TheMaster

  • Guest
Re: How to get Entities in a Block.
« Reply #28 on: February 08, 2012, 05:40:33 PM »

Did you quit playing?


No, I've been busy building a new system for Android and iOS development with Mono For Android (http://xamarin.com/monoforandroid) and haven't been playing with much else

Regarding this, I was just trying to save you the embarrassment resulting from finding out that trying to show messages using an API that is inherently not thread-safe (MessageBox.Show) doesn't always work when it is run on a high-priority, background thread with no message pump (the GC's thread), and there will be no exception or any other indication that it failed (other than AutoCAD crashing :-P). For that matter, we're not supposed to do any kind of blocking UI calls in the GC's thread.

Just guessing here, but I think the reason why MessagBox.Show(), ShowAlertDialog(), etc, may not work or even may crash AutoCAD, has to do with the hooks AutoCAD uses to disable/enable floating windows whenever a modal dialog is shown/dismissed. If I'm not mistaken, that involves accessing state and APIs that cannot be accessed asynchronously, from a non-AutoCAD thread.

Did you not see my comment regarding the fact that even acutPrintF() fails in that context?  :wink:

I lost count of how many classes I've made that are based on DisposableWrapper, and every one of their implementations of DeleteUnmanagedObject() is called on the next GC (in the thread of the GC) after they become unreachable when Dispose() was not called on them. I know that, because from my overrides of their Dispose(bool) methods, I usually throw an exception in debug builds, to tell me that I failed to deterministically dispose the instance. :wink:

So, you might be mistaking the failure of MessageBox.Show() when run on the GC thread, with the presumption that the finalizer is not running, or is not calling DeleteUnmanagedObject().  It certainly is and does, and you can confirm that by using a thread-safe way of showing the message (even the debugger may not hit a breakbpoint in that context, BTW). 

I use a homegrown trace utility that outputs trace messages synchronously to another process via SendMessage(WM_COPYDATA,...) that doesn't return until the message has actually been shown in the other process (the trace listener, which I wrote in Delphi about 10 years ago and can't find the source code for now  :oops:).

As Jeff's experiment shows,when you tell the runtime to not do garbage collection asynchronously, everything is fine, except that it will most-likely result in severe 'lag' in the UI, due to the fact that AutoCAD must stop and wait for a GC to complete.

« Last Edit: February 09, 2012, 06:36:08 AM by TheMaster »

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8691
  • AKA Daniel
Re: How to get Entities in a Block.
« Reply #29 on: February 10, 2012, 01:55:00 AM »
I think you got lost  :lol:

I get that items such as acutPrintf are not thread safe. it's not even close to what I am taking about. I am talking about the "lifetime" of an underlying unmanaged object, when wrapped in a class such as disposableWapper.  I don't care what thread does the cleanup, I only care the state of the object. I made a suggestion that .NET has no clue how or when to deal with unmanaged objects, hence "unmanaged", you disagreed,  I created a sample to prove a point, but you never did run it.  so you can't tell me the behavior  :-P

My goal here is not to argue with you, it's to pass on the little knowledge i have gained from writing literally thousands of wrappers for .NET : ) .  Some interesting classes relating to disposablewrapper, Autodesk.AutoCAD.Windows.Window (which implements disposablewrapper) and its subclass Autodesk.AutoCAD.Windows.NonFinalizableWindow  8-)


cheers .